/* $OpenBSD: bwfm.c,v 1.106 2022/12/30 16:49:34 kettenis Exp $ */
/*
 * Copyright (c) 2010-2016 Broadcom Corporation
 * Copyright (c) 2016,2017 Patrick Wildt <patrick@blueri.se>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "bpfilter.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/sockio.h>

#if defined(__HAVE_FDT)
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#endif

#if NBPFILTER > 0
#include <net/bpf.h>
#endif
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>

#include <netinet/in.h>
#include <netinet/if_ether.h>

#include <net80211/ieee80211_var.h>

#include <dev/ic/bwfmvar.h>
#include <dev/ic/bwfmreg.h>

/* #define BWFM_DEBUG */
#ifdef BWFM_DEBUG
#define DPRINTF(x)    do { if (bwfm_debug > 0) printf x; } while (0)
#define DPRINTFN(n, x)    do { if (bwfm_debug >= (n)) printf x; } while (0)
static int bwfm_debug = 1;
#else
#define DPRINTF(x)    do { ; } while (0)
#define DPRINTFN(n, x)    do { ; } while (0)
#endif

#define DEVNAME(sc)    ((sc)->sc_dev.dv_xname)

void     bwfm_start(struct ifnet *);
void     bwfm_init(struct ifnet *);
void     bwfm_stop(struct ifnet *);
void     bwfm_iff(struct bwfm_softc *);
void     bwfm_watchdog(struct ifnet *);
void     bwfm_update_node(void *, struct ieee80211_node *);
void     bwfm_update_nodes(struct bwfm_softc *);
int     bwfm_ioctl(struct ifnet *, u_long, caddr_t);
int     bwfm_media_change(struct ifnet *);

void     bwfm_init_board_type(struct bwfm_softc *);
void     bwfm_process_blob(struct bwfm_softc *, char *, u_char **, size_t *);

int     bwfm_chip_attach(struct bwfm_softc *);
void     bwfm_chip_detach(struct bwfm_softc *);
struct bwfm_core *bwfm_chip_get_core_idx(struct bwfm_softc *, int, int);
struct bwfm_core *bwfm_chip_get_core(struct bwfm_softc *, int);
struct bwfm_core *bwfm_chip_get_pmu(struct bwfm_softc *);
int     bwfm_chip_ai_isup(struct bwfm_softc *, struct bwfm_core *);
void     bwfm_chip_ai_disable(struct bwfm_softc *, struct bwfm_core *,
         uint32_t, uint32_t);
void     bwfm_chip_ai_reset(struct bwfm_softc *, struct bwfm_core *,
         uint32_t, uint32_t, uint32_t);
void     bwfm_chip_dmp_erom_scan(struct bwfm_softc *);
int     bwfm_chip_dmp_get_regaddr(struct bwfm_softc *, uint32_t *,
         uint32_t *, uint32_t *);
int     bwfm_chip_cr4_set_active(struct bwfm_softc *, uint32_t);
void     bwfm_chip_cr4_set_passive(struct bwfm_softc *);
int     bwfm_chip_ca7_set_active(struct bwfm_softc *, uint32_t);
void     bwfm_chip_ca7_set_passive(struct bwfm_softc *);
int     bwfm_chip_cm3_set_active(struct bwfm_softc *);
void     bwfm_chip_cm3_set_passive(struct bwfm_softc *);
void     bwfm_chip_socram_ramsize(struct bwfm_softc *, struct bwfm_core *);
void     bwfm_chip_sysmem_ramsize(struct bwfm_softc *, struct bwfm_core *);
void     bwfm_chip_tcm_ramsize(struct bwfm_softc *, struct bwfm_core *);
void     bwfm_chip_tcm_rambase(struct bwfm_softc *);

int     bwfm_proto_bcdc_query_dcmd(struct bwfm_softc *, int,
         int, char *, size_t *);
int     bwfm_proto_bcdc_set_dcmd(struct bwfm_softc *, int,
         int, char *, size_t);
void     bwfm_proto_bcdc_rx(struct bwfm_softc *, mbuf_t,
         struct mbuf_list *);
int     bwfm_proto_bcdc_txctl(struct bwfm_softc *, int, char *, size_t *);
void     bwfm_proto_bcdc_rxctl(struct bwfm_softc *, char *, size_t);

int     bwfm_fwvar_cmd_get_data(struct bwfm_softc *, int, void *, size_t);
int     bwfm_fwvar_cmd_set_data(struct bwfm_softc *, int, void *, size_t);
int     bwfm_fwvar_cmd_get_int(struct bwfm_softc *, int, uint32_t *);
int     bwfm_fwvar_cmd_set_int(struct bwfm_softc *, int, uint32_t);
int     bwfm_fwvar_var_get_data(struct bwfm_softc *, char *, void *, size_t);
int     bwfm_fwvar_var_set_data(struct bwfm_softc *, char *, void *, size_t);
int     bwfm_fwvar_var_get_int(struct bwfm_softc *, char *, uint32_t *);
int     bwfm_fwvar_var_set_int(struct bwfm_softc *, char *, uint32_t);

uint32_t bwfm_chan2spec(struct bwfm_softc *, struct ieee80211_channel *);
uint32_t bwfm_chan2spec_d11n(struct bwfm_softc *, struct ieee80211_channel *);
uint32_t bwfm_chan2spec_d11ac(struct bwfm_softc *, struct ieee80211_channel *);
uint32_t bwfm_spec2chan(struct bwfm_softc *, uint32_t);
uint32_t bwfm_spec2chan_d11n(struct bwfm_softc *, uint32_t);
uint32_t bwfm_spec2chan_d11ac(struct bwfm_softc *, uint32_t);

void     bwfm_connect(struct bwfm_softc *);
#ifndef IEEE80211_STA_ONLY
void     bwfm_hostap(struct bwfm_softc *);
#endif
void     bwfm_scan(struct bwfm_softc *);
void     bwfm_scan_abort(struct bwfm_softc *);

void     bwfm_task(void *);
void     bwfm_do_async(struct bwfm_softc *,
         void (*)(struct bwfm_softc *, void *), void *, int);

int     bwfm_set_key(struct ieee80211com *, struct ieee80211_node *,
         struct ieee80211_key *);
void     bwfm_delete_key(struct ieee80211com *, struct ieee80211_node *,
         struct ieee80211_key *);
int     bwfm_send_mgmt(struct ieee80211com *, struct ieee80211_node *,
         int, int, int);
int     bwfm_newstate(struct ieee80211com *, enum ieee80211_state, int);

void     bwfm_set_key_cb(struct bwfm_softc *, void *);
void     bwfm_delete_key_cb(struct bwfm_softc *, void *);
void     bwfm_rx_event_cb(struct bwfm_softc *, mbuf_t);

mbuf_t     bwfm_newbuf(void);
#ifndef IEEE80211_STA_ONLY
void     bwfm_rx_auth_ind(struct bwfm_softc *, struct bwfm_event *, size_t);
void     bwfm_rx_assoc_ind(struct bwfm_softc *, struct bwfm_event *, size_t, int);
void     bwfm_rx_deauth_ind(struct bwfm_softc *, struct bwfm_event *, size_t);
void     bwfm_rx_disassoc_ind(struct bwfm_softc *, struct bwfm_event *, size_t);
void     bwfm_rx_leave_ind(struct bwfm_softc *, struct bwfm_event *, size_t, int);
#endif
void     bwfm_rx_event(struct bwfm_softc *, mbuf_t);
void     bwfm_scan_node(struct bwfm_softc *, struct bwfm_bss_info *, size_t);

extern void ieee80211_node2req(struct ieee80211com *,
         const struct ieee80211_node *, struct ieee80211_nodereq *);
extern void ieee80211_req2node(struct ieee80211com *,
         const struct ieee80211_nodereq *, struct ieee80211_node *);

uint8_t bwfm_2ghz_channels[] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
};
uint8_t bwfm_5ghz_channels[] = {
    34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64, 100, 104, 108, 112,
    116, 120, 124, 128, 132, 136, 140, 144, 149, 153, 157, 161, 165,
};

struct bwfm_proto_ops bwfm_proto_bcdc_ops = {
    .proto_query_dcmd = bwfm_proto_bcdc_query_dcmd,
    .proto_set_dcmd = bwfm_proto_bcdc_set_dcmd,
    .proto_rx = bwfm_proto_bcdc_rx,
    .proto_rxctl = bwfm_proto_bcdc_rxctl,
};

struct cfdriver bwfm_cd = {
    NULL, "bwfm", DV_IFNET
};

void
bwfm_attach(struct bwfm_softc *sc)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct ifnet *ifp = &ic->ic_if;

    TAILQ_INIT(&sc->sc_bcdc_rxctlq);

    /* Init host async commands ring. */
    sc->sc_cmdq.cur = sc->sc_cmdq.next = sc->sc_cmdq.queued = 0;
    sc->sc_taskq = taskq_create(DEVNAME(sc), 1, IPL_SOFTNET, 0);
    task_set(&sc->sc_task, bwfm_task, sc);
    ml_init(&sc->sc_evml);

    ic->ic_phytype = IEEE80211_T_OFDM;    /* not only, but not used */
    ic->ic_opmode = IEEE80211_M_STA;    /* default to BSS mode */
    ic->ic_state = IEEE80211_S_INIT;

    ic->ic_caps =
#ifndef IEEE80211_STA_ONLY
        IEEE80211_C_HOSTAP |    /* Access Point */
#endif
        IEEE80211_C_RSN |         /* WPA/RSN */
        IEEE80211_C_SCANALL |    /* device scans all channels at once */
        IEEE80211_C_SCANALLBAND;    /* device scans all bands at once */

    /* IBSS channel undefined for now. */
    ic->ic_ibss_chan = &ic->ic_channels[0];

    ifp->if_softc = sc;
    ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
    ifp->if_ioctl = bwfm_ioctl;
    ifp->if_start = bwfm_start;
    ifp->if_watchdog = bwfm_watchdog;
    memcpy(ifp->if_xname, DEVNAME(sc), IFNAMSIZ);

    if_attach(ifp);
    ieee80211_ifattach(ifp);

    sc->sc_newstate = ic->ic_newstate;
    ic->ic_newstate = bwfm_newstate;
    ic->ic_send_mgmt = bwfm_send_mgmt;
    ic->ic_set_key = bwfm_set_key;
    ic->ic_delete_key = bwfm_delete_key;

    ieee80211_media_init(ifp, bwfm_media_change, ieee80211_media_status);
}

void
bwfm_attachhook(struct device *self)
{
    struct bwfm_softc *sc = (struct bwfm_softc *)self;

    if (sc->sc_bus_ops->bs_preinit != NULL &&
        sc->sc_bus_ops->bs_preinit(sc))
        return;
    if (bwfm_preinit(sc))
        return;
    sc->sc_initialized = 1;
}

int
bwfm_preinit(struct bwfm_softc *sc)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct ifnet *ifp = &ic->ic_if;
    int i, j, nbands, nmode, vhtmode;
    uint32_t bandlist[3], tmp;

    if (sc->sc_initialized)
        return 0;

    if (bwfm_fwvar_cmd_get_int(sc, BWFM_C_GET_VERSION, &tmp)) {
        printf("%s: could not read io type\n", DEVNAME(sc));
        return 1;
    } else
        sc->sc_io_type = tmp;
    if (bwfm_fwvar_var_get_data(sc, "cur_etheraddr", ic->ic_myaddr,
        sizeof(ic->ic_myaddr))) {
        printf("%s: could not read mac address\n", DEVNAME(sc));
        return 1;
    }

    printf("%s: address %s\n", DEVNAME(sc), ether_sprintf(ic->ic_myaddr));

    bwfm_process_blob(sc, "clmload", &sc->sc_clm, &sc->sc_clmsize);
    bwfm_process_blob(sc, "txcapload", &sc->sc_txcap, &sc->sc_txcapsize);
    bwfm_process_blob(sc, "calload", &sc->sc_cal, &sc->sc_calsize);

    if (bwfm_fwvar_var_get_int(sc, "nmode", (uint32_t *)&nmode))
        nmode = 0;
    if (bwfm_fwvar_var_get_int(sc, "vhtmode", (uint32_t *)&vhtmode))
        vhtmode = 0;
    if (bwfm_fwvar_var_get_int(sc, "scan_ver", (uint32_t *)&sc->sc_scan_ver))
        sc->sc_scan_ver = 0;
    if (bwfm_fwvar_cmd_get_data(sc, BWFM_C_GET_BANDLIST, bandlist,
        sizeof(bandlist))) {
        printf("%s: couldn't get supported band list\n", DEVNAME(sc));
        return 1;
    }
    nbands = letoh32(bandlist[0]);
    for (i = 1; i <= nbands && i < nitems(bandlist); i++) {
        switch (letoh32(bandlist[i])) {
        case BWFM_BAND_2G:
            DPRINTF(("%s: 2G HT %d VHT %d\n",
                DEVNAME(sc), nmode, vhtmode));
            ic->ic_sup_rates[IEEE80211_MODE_11B] =
                ieee80211_std_rateset_11b;
            ic->ic_sup_rates[IEEE80211_MODE_11G] =
                ieee80211_std_rateset_11g;

            for (j = 0; j < nitems(bwfm_2ghz_channels); j++) {
                uint8_t chan = bwfm_2ghz_channels[j];
                ic->ic_channels[chan].ic_freq =
                    ieee80211_ieee2mhz(chan, IEEE80211_CHAN_2GHZ);
                ic->ic_channels[chan].ic_flags =
                    IEEE80211_CHAN_CCK | IEEE80211_CHAN_OFDM |
                    IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ;
                if (nmode)
                    ic->ic_channels[chan].ic_flags |=
                        IEEE80211_CHAN_HT;
                /* VHT is 5GHz only */
            }
            break;
        case BWFM_BAND_5G:
            DPRINTF(("%s: 5G HT %d VHT %d\n",
                DEVNAME(sc), nmode, vhtmode));
            ic->ic_sup_rates[IEEE80211_MODE_11A] =
                ieee80211_std_rateset_11a;

            for (j = 0; j < nitems(bwfm_5ghz_channels); j++) {
                uint8_t chan = bwfm_5ghz_channels[j];
                ic->ic_channels[chan].ic_freq =
                    ieee80211_ieee2mhz(chan, IEEE80211_CHAN_5GHZ);
                ic->ic_channels[chan].ic_flags =
                    IEEE80211_CHAN_A;
                if (nmode)
                    ic->ic_channels[chan].ic_flags |=
                        IEEE80211_CHAN_HT;
                if (vhtmode)
                    ic->ic_channels[chan].ic_flags |=
                        IEEE80211_CHAN_VHT;
            }
            break;
        default:
            printf("%s: unsupported band 0x%x\n", DEVNAME(sc),
                letoh32(bandlist[i]));
            break;
        }
    }

    /* Configure channel information obtained from firmware. */
    ieee80211_channel_init(ifp);

    /* Configure MAC address. */
    if (if_setlladdr(ifp, ic->ic_myaddr))
        printf("%s: could not set MAC address\n", DEVNAME(sc));

    ieee80211_media_init(ifp, bwfm_media_change, ieee80211_media_status);
    return 0;
}

void
bwfm_cleanup(struct bwfm_softc *sc)
{
    bwfm_chip_detach(sc);
    sc->sc_initialized = 0;
}

int
bwfm_detach(struct bwfm_softc *sc, int flags)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct ifnet *ifp = &ic->ic_if;

    task_del(sc->sc_taskq, &sc->sc_task);
    ieee80211_ifdetach(ifp);
    taskq_barrier(sc->sc_taskq);
    if_detach(ifp);
    taskq_destroy(sc->sc_taskq);

    bwfm_cleanup(sc);
    return 0;
}

int
bwfm_activate(struct bwfm_softc *sc, int act)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct ifnet *ifp = &ic->ic_if;

    switch (act) {
    case DVACT_QUIESCE:
        if (ifp->if_flags & IFF_UP)
            bwfm_stop(ifp);
        break;
    case DVACT_WAKEUP:
        if (ifp->if_flags & IFF_UP)
            bwfm_init(ifp);
        break;
    default:
        break;
    }

    return 0;
}

void
bwfm_start(struct ifnet *ifp)
{
    struct bwfm_softc *sc = (typeof sc)ifp->if_softc;
    struct ieee80211com *ic = &sc->sc_ic;
    mbuf_t m;

    if (!(ifp->if_flags & IFF_RUNNING))
        return;
    if (ifq_is_oactive(&ifp->if_snd))
        return;
    if (ifq_empty(&ifp->if_snd))
        return;

    /* TODO: return if no link? */

    for (;;) {
        if (sc->sc_bus_ops->bs_txcheck(sc)) {
            ifq_set_oactive(&ifp->if_snd);
            break;
        }

        if (!((struct device *)ifp->if_softc)->dev->useAppleRSNSupplicant(ifp->iface))
        if (ic->ic_state != IEEE80211_S_RUN ||
            (ic->ic_xflags & IEEE80211_F_TX_MGMT_ONLY))
            break;

        m = ifq_dequeue(&ifp->if_snd);
        if (m == NULL)
            break;

        if (sc->sc_bus_ops->bs_txdata(sc, m) != 0) {
            ifp->if_oerrors++;
            m_freem(m);
            continue;
        }

#if NBPFILTER > 0
        if (ifp->if_bpf)
            bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT);
#endif
    }
}

void
bwfm_init(struct ifnet *ifp)
{
    struct bwfm_softc *sc = (typeof sc)ifp->if_softc;
    struct ieee80211com *ic = &sc->sc_ic;
    uint8_t evmask[BWFM_EVENT_MASK_LEN];
    struct bwfm_join_pref_params join_pref[2];
    int pm;

    if (!sc->sc_initialized) {
        if (sc->sc_bus_ops->bs_preinit != NULL &&
            sc->sc_bus_ops->bs_preinit(sc)) {
            printf("%s: could not init bus\n", DEVNAME(sc));
            return;
        }
        if (bwfm_preinit(sc)) {
            printf("%s: could not init\n", DEVNAME(sc));
            return;
        }
        sc->sc_initialized = 1;
    }

    /* Select default channel */
    ic->ic_bss->ni_chan = ic->ic_ibss_chan;

    if (bwfm_fwvar_var_set_int(sc, "mpc", 1)) {
        printf("%s: could not set mpc\n", DEVNAME(sc));
        return;
    }

    /* Select target by RSSI (boost on 5GHz) */
    join_pref[0].type = BWFM_JOIN_PREF_RSSI_DELTA;
    join_pref[0].len = 2;
    join_pref[0].rssi_gain = BWFM_JOIN_PREF_RSSI_BOOST;
    join_pref[0].band = BWFM_JOIN_PREF_BAND_5G;
    join_pref[1].type = BWFM_JOIN_PREF_RSSI;
    join_pref[1].len = 2;
    join_pref[1].rssi_gain = 0;
    join_pref[1].band = 0;
    if (bwfm_fwvar_var_set_data(sc, "join_pref", join_pref,
        sizeof(join_pref))) {
        printf("%s: could not set join pref\n", DEVNAME(sc));
        return;
    }

#define BWFM_EVENT(event) evmask[(event) / 8] |= 1 << ((event) % 8)
    memset(evmask, 0, sizeof(evmask));
    switch (ic->ic_opmode) {
    case IEEE80211_M_STA:
        BWFM_EVENT(BWFM_E_IF);
        BWFM_EVENT(BWFM_E_LINK);
        BWFM_EVENT(BWFM_E_AUTH);
        BWFM_EVENT(BWFM_E_ASSOC);
        BWFM_EVENT(BWFM_E_DEAUTH);
        BWFM_EVENT(BWFM_E_DISASSOC);
        BWFM_EVENT(BWFM_E_ESCAN_RESULT);
        break;
#ifndef IEEE80211_STA_ONLY
    case IEEE80211_M_HOSTAP:
        BWFM_EVENT(BWFM_E_AUTH_IND);
        BWFM_EVENT(BWFM_E_ASSOC_IND);
        BWFM_EVENT(BWFM_E_REASSOC_IND);
        BWFM_EVENT(BWFM_E_DEAUTH_IND);
        BWFM_EVENT(BWFM_E_DISASSOC_IND);
        BWFM_EVENT(BWFM_E_ESCAN_RESULT);
        break;
#endif
    default:
        break;
    }
#undef BWFM_EVENT

    if (bwfm_fwvar_var_set_data(sc, "event_msgs", evmask, sizeof(evmask))) {
        printf("%s: could not set event mask\n", DEVNAME(sc));
        return;
    }

    if (bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_SCAN_CHANNEL_TIME,
        BWFM_DEFAULT_SCAN_CHANNEL_TIME)) {
        printf("%s: could not set scan channel time\n", DEVNAME(sc));
        return;
    }
    if (bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_SCAN_UNASSOC_TIME,
        BWFM_DEFAULT_SCAN_UNASSOC_TIME)) {
        printf("%s: could not set scan unassoc time\n", DEVNAME(sc));
        return;
    }
    if (bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_SCAN_PASSIVE_TIME,
        BWFM_DEFAULT_SCAN_PASSIVE_TIME)) {
        printf("%s: could not set scan passive time\n", DEVNAME(sc));
        return;
    }

    /*
     * Use CAM (constantly awake) when we are running as AP,
     * otherwise use fast power saving.
     */
    pm = BWFM_PM_FAST_PS;
#ifndef IEEE80211_STA_ONLY
    if (ic->ic_opmode == IEEE80211_M_HOSTAP)
        pm = BWFM_PM_CAM;
#endif
    if (bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PM, pm)) {
        printf("%s: could not set power\n", DEVNAME(sc));
        return;
    }

    bwfm_fwvar_var_set_int(sc, "txbf", 1);
    bwfm_fwvar_cmd_set_int(sc, BWFM_C_UP, 0);
    bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_INFRA, 1);
    bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_AP, 0);

    /* Disable all offloading (ARP, NDP, TCP/UDP cksum). */
    bwfm_fwvar_var_set_int(sc, "arp_ol", 0);
    bwfm_fwvar_var_set_int(sc, "arpoe", 0);
    bwfm_fwvar_var_set_int(sc, "ndoe", 0);
    bwfm_fwvar_var_set_int(sc, "toe", 0);

    /*
     * The firmware supplicant can handle the WPA handshake for
     * us, but we honestly want to do this ourselves, so disable
     * the firmware supplicant and let our stack handle it.
     */
    bwfm_fwvar_var_set_int(sc, "sup_wpa", 0);

    bwfm_iff(sc);

    ifp->if_flags |= IFF_RUNNING;
    ifq_clr_oactive(&ifp->if_snd);

    ieee80211_begin_scan(ifp);
}

void
bwfm_stop(struct ifnet *ifp)
{
    struct bwfm_softc *sc = (typeof sc)ifp->if_softc;
    struct ieee80211com *ic = &sc->sc_ic;
    struct bwfm_join_params join;

    sc->sc_tx_timer = 0;
    ifp->if_timer = 0;
    ifp->if_flags &= ~IFF_RUNNING;
    ifq_clr_oactive(&ifp->if_snd);

    ieee80211_new_state(ic, IEEE80211_S_INIT, -1);

    memset(&join, 0, sizeof(join));
    bwfm_fwvar_cmd_set_data(sc, BWFM_C_SET_SSID, &join, sizeof(join));
    bwfm_fwvar_cmd_set_int(sc, BWFM_C_DOWN, 1);
    bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_AP, 0);
    bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_INFRA, 0);
    bwfm_fwvar_cmd_set_int(sc, BWFM_C_UP, 1);
    bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PM, BWFM_PM_FAST_PS);
    bwfm_fwvar_var_set_int(sc, "mpc", 1);

    if (sc->sc_bus_ops->bs_stop)
        sc->sc_bus_ops->bs_stop(sc);
}

void
bwfm_iff(struct bwfm_softc *sc)
{
    struct arpcom *ac = &sc->sc_ic.ic_ac;
    struct ifnet *ifp = &ac->ac_if;
    struct ether_multi *enm;
    struct ether_multistep step;
    size_t mcastlen;
    char *mcast;
    int i = 0;

    mcastlen = sizeof(uint32_t) + ac->ac_multicnt * ETHER_ADDR_LEN;
    mcast = (typeof mcast)malloc(mcastlen, M_TEMP, M_WAITOK);
    htolem32((uint32_t *)mcast, ac->ac_multicnt);

    ifp->if_flags &= ~IFF_ALLMULTI;
    if (ifp->if_flags & IFF_PROMISC || ac->ac_multirangecnt > 0) {
        ifp->if_flags |= IFF_ALLMULTI;
    } else {
        ETHER_FIRST_MULTI(step, ac, enm);
        while (enm != NULL) {
            memcpy(mcast + sizeof(uint32_t) + i * ETHER_ADDR_LEN,
                enm->enm_addrlo, ETHER_ADDR_LEN);
            ETHER_NEXT_MULTI(step, enm);
            i++;
        }
    }

    bwfm_fwvar_var_set_data(sc, "mcast_list", mcast, mcastlen);
    bwfm_fwvar_var_set_int(sc, "allmulti",
        !!(ifp->if_flags & IFF_ALLMULTI));
    bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_PROMISC,
        !!(ifp->if_flags & IFF_PROMISC));

    free(mcast, M_TEMP, mcastlen);
}

void
bwfm_watchdog(struct ifnet *ifp)
{
    struct bwfm_softc *sc = (typeof sc)ifp->if_softc;

    ifp->if_timer = 0;

    if (sc->sc_tx_timer > 0) {
        if (--sc->sc_tx_timer == 0) {
            printf("%s: device timeout\n", DEVNAME(sc));
            ifp->if_oerrors++;
            return;
        }
        ifp->if_timer = 1;
    }
    ieee80211_watchdog(ifp);
}

/*
 * Tx-rate to MCS conversion might lie since some rates map to multiple MCS.
 * But this is the best we can do given that firmware only reports kbit/s.
 */

void
bwfm_rate2vhtmcs(int *mcs, int *ss, uint32_t txrate)
{
    const struct ieee80211_vht_rateset *rs;
    int i, j;
    
    *mcs = -1;
    *ss = -1;
    /* TODO: Select specific ratesets based on BSS channel width. */
    for (i = 0; i < IEEE80211_VHT_NUM_RATESETS; i++) {
        rs = &ieee80211_std_ratesets_11ac[i];
        for (j = 0; j < rs->nrates; j++) {
            if (rs->rates[j] == txrate / 500) {
                *mcs = j;
                *ss = rs->num_ss;
                return;
            }
        }
    }
}

int
bwfm_rate2htmcs(uint32_t txrate)
{
    const struct ieee80211_ht_rateset *rs;
    int i, j;
    
    /* TODO: Select specific ratesets based on BSS channel width. */
    for (i = 0; i < IEEE80211_HT_NUM_RATESETS; i++) {
        rs = &ieee80211_std_ratesets_11n[i];
        for (j = 0; j < rs->nrates; j++) {
            if (rs->rates[j] == txrate / 500)
                return rs->min_mcs + j;
        }
    }

    return -1;
}

void
bwfm_update_node(void *arg, struct ieee80211_node *ni)
{
    struct bwfm_softc *sc = (typeof sc)arg;
    struct ieee80211com *ic = &sc->sc_ic;
    struct bwfm_sta_info sta;
    uint32_t flags;
    int8_t rssi;
    uint32_t txrate;
    int i;

    memset(&sta, 0, sizeof(sta));
    memcpy((uint8_t *)&sta, ni->ni_macaddr, sizeof(ni->ni_macaddr));

    if (bwfm_fwvar_var_get_data(sc, "sta_info", &sta, sizeof(sta)))
        return;

    if (!IEEE80211_ADDR_EQ(ni->ni_macaddr, sta.ea))
        return;

    if (le16toh(sta.ver) < 3)
        return;

    flags = le32toh(sta.flags);
    if ((flags & BWFM_STA_SCBSTATS) == 0)
        return;

    if (le16toh(sta.ver) >= 4) {
        rssi = 0;
        for (i = 0; i < BWFM_ANT_MAX; i++) {
            if (sta.rssi[i] >= 0)
                continue;
            if (rssi == 0 || sta.rssi[i] > rssi)
                rssi = sta.rssi[i];
        }
        if (rssi)
            ni->ni_rssi = rssi;
    }

    txrate = le32toh(sta.tx_rate); /* in kbit/s */
    if (txrate == 0xffffffff) /* Seen this happening during association. */
        return;

    if ((le32toh(sta.flags) & BWFM_STA_VHT_CAP)) {
        int mcs, ss;
        /* Tell net80211 that firmware has negotiated 11ac. */
        ni->ni_flags |= IEEE80211_NODE_VHT;
        ni->ni_flags |= IEEE80211_NODE_HT; /* VHT implies HT support */
        if (ic->ic_curmode < IEEE80211_MODE_11AC)
            ieee80211_setmode(ic, IEEE80211_MODE_11AC);
            bwfm_rate2vhtmcs(&mcs, &ss, txrate);
        if (mcs >= 0) {
            ni->ni_txmcs = mcs;
            ni->ni_vht_ss = ss;
        } else {
            ni->ni_txmcs = 0;
            ni->ni_vht_ss = 1;
        }
    } else if ((le32toh(sta.flags) & BWFM_STA_N_CAP)) {
        int mcs;
        /* Tell net80211 that firmware has negotiated 11n. */
        ni->ni_flags |= IEEE80211_NODE_HT;
        if (ic->ic_curmode < IEEE80211_MODE_11N)
            ieee80211_setmode(ic, IEEE80211_MODE_11N);
            mcs = bwfm_rate2htmcs(txrate);
        ni->ni_txmcs = (mcs >= 0 ? mcs : 0);
    } else {
        /* We're in 11a/g mode. Map to a legacy rate. */
        for (i = 0; i < ni->ni_rates.rs_nrates; i++) {
            uint8_t rate = ni->ni_rates.rs_rates[i];
            rate &= IEEE80211_RATE_VAL;
            if (rate == txrate / 500) {
                ni->ni_txrate = i;
                break;
            }
        }
    }
}

void
bwfm_update_nodes(struct bwfm_softc *sc)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct ieee80211_node *ni;

    switch (ic->ic_opmode) {
    case IEEE80211_M_STA:
        bwfm_update_node(sc, ic->ic_bss);
        /* Update cached copy in the nodes tree as well. */
        ni = ieee80211_find_node(ic, ic->ic_bss->ni_macaddr);
        if (ni) {
            ni->ni_rssi = ic->ic_bss->ni_rssi;
        }
        break;
#ifndef IEEE80211_STA_ONLY
    case IEEE80211_M_HOSTAP:
        ieee80211_iterate_nodes(ic, bwfm_update_node, sc);
        break;
#endif
    default:
        break;
    }
}

int
bwfm_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
    struct bwfm_softc *sc = (typeof sc)ifp->if_softc;
    struct ieee80211com *ic = &sc->sc_ic;
    struct ifreq *ifr;
    int s, error = 0;

    s = splnet();
    switch (cmd) {
    case SIOCSIFADDR:
        ifp->if_flags |= IFF_UP;
        /* FALLTHROUGH */
    case SIOCSIFFLAGS:
        if (ifp->if_flags & IFF_UP) {
            if (!(ifp->if_flags & IFF_RUNNING))
                bwfm_init(ifp);
        } else {
            if (ifp->if_flags & IFF_RUNNING)
                bwfm_stop(ifp);
        }
        break;
    case SIOCADDMULTI:
    case SIOCDELMULTI:
//        ifr = (struct ifreq *)data;
//        error = (cmd == SIOCADDMULTI) ?
//            ether_addmulti(ifr, &ic->ic_ac) :
//            ether_delmulti(ifr, &ic->ic_ac);
//        if (error == ENETRESET) {
//            bwfm_iff(sc);
//            error = 0;
//        }
        break;
    case SIOCGIFMEDIA:
    case SIOCG80211NODE:
    case SIOCG80211ALLNODES:
        if (ic->ic_state == IEEE80211_S_RUN)
            bwfm_update_nodes(sc);
        /* fall through */
    default:
        error = ieee80211_ioctl(ifp, cmd, data);
    }
    if (error == ENETRESET) {
        if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) ==
            (IFF_UP | IFF_RUNNING)) {
            bwfm_stop(ifp);
            bwfm_init(ifp);
        }
        error = 0;
    }
    splx(s);
    return error;
}

int
bwfm_media_change(struct ifnet *ifp)
{
    int error;

    error = ieee80211_media_change(ifp);
    if (error != ENETRESET)
        return error;

    if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) ==
        (IFF_UP | IFF_RUNNING)) {
        bwfm_stop(ifp);
        bwfm_init(ifp);
    }
    return error;
}

/* Chip initialization (SDIO, PCIe) */
int
bwfm_chip_attach(struct bwfm_softc *sc)
{
    struct bwfm_core *core;
    int need_socram = 0;
    int has_socram = 0;
    int cpu_found = 0;
    uint32_t val;

    LIST_INIT(&sc->sc_chip.ch_list);

    if (sc->sc_buscore_ops->bc_prepare(sc) != 0) {
        printf("%s: failed buscore prepare\n", DEVNAME(sc));
        return 1;
    }

    val = sc->sc_buscore_ops->bc_read(sc,
        BWFM_CHIP_BASE + BWFM_CHIP_REG_CHIPID);
    sc->sc_chip.ch_chip = BWFM_CHIP_CHIPID_ID(val);
    sc->sc_chip.ch_chiprev = BWFM_CHIP_CHIPID_REV(val);

    if ((sc->sc_chip.ch_chip > 0xa000) || (sc->sc_chip.ch_chip < 0x4000))
        snprintf(sc->sc_chip.ch_name, sizeof(sc->sc_chip.ch_name),
            "%d", sc->sc_chip.ch_chip);
    else
        snprintf(sc->sc_chip.ch_name, sizeof(sc->sc_chip.ch_name),
            "%x", sc->sc_chip.ch_chip);

    switch (BWFM_CHIP_CHIPID_TYPE(val))
    {
    case BWFM_CHIP_CHIPID_TYPE_SOCI_SB:
        printf("%s: SoC interconnect SB not implemented\n",
            DEVNAME(sc));
        return 1;
    case BWFM_CHIP_CHIPID_TYPE_SOCI_AI:
        sc->sc_chip.ch_core_isup = bwfm_chip_ai_isup;
        sc->sc_chip.ch_core_disable = bwfm_chip_ai_disable;
        sc->sc_chip.ch_core_reset = bwfm_chip_ai_reset;
        bwfm_chip_dmp_erom_scan(sc);
        break;
    default:
        printf("%s: SoC interconnect %d unknown\n",
            DEVNAME(sc), BWFM_CHIP_CHIPID_TYPE(val));
        return 1;
    }

    LIST_FOREACH(core, &sc->sc_chip.ch_list, co_link) {
        DPRINTF(("%s: 0x%x:%-2d base 0x%08x wrap 0x%08x\n",
            DEVNAME(sc), core->co_id, core->co_rev,
            core->co_base, core->co_wrapbase));

        switch (core->co_id) {
        case BWFM_AGENT_CORE_ARM_CM3:
            need_socram = 1;
            /* FALLTHROUGH */
        case BWFM_AGENT_CORE_ARM_CR4:
        case BWFM_AGENT_CORE_ARM_CA7:
            cpu_found = 1;
            break;
        case BWFM_AGENT_INTERNAL_MEM:
            has_socram = 1;
            break;
        default:
            break;
        }
    }

    if (!cpu_found) {
        printf("%s: CPU core not detected\n", DEVNAME(sc));
        return 1;
    }
    if (need_socram && !has_socram) {
        printf("%s: RAM core not provided\n", DEVNAME(sc));
        return 1;
    }

    bwfm_chip_set_passive(sc);

    if (sc->sc_buscore_ops->bc_reset) {
        sc->sc_buscore_ops->bc_reset(sc);
        bwfm_chip_set_passive(sc);
    }

    if ((core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4)) != NULL) {
        bwfm_chip_tcm_ramsize(sc, core);
        bwfm_chip_tcm_rambase(sc);
    } else if ((core = bwfm_chip_get_core(sc, BWFM_AGENT_SYS_MEM)) != NULL) {
        bwfm_chip_sysmem_ramsize(sc, core);
        bwfm_chip_tcm_rambase(sc);
    } else if ((core = bwfm_chip_get_core(sc, BWFM_AGENT_INTERNAL_MEM)) != NULL) {
        bwfm_chip_socram_ramsize(sc, core);
    }

    core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_CHIPCOMMON);
    sc->sc_chip.ch_cc_caps = sc->sc_buscore_ops->bc_read(sc,
        core->co_base + BWFM_CHIP_REG_CAPABILITIES);
    sc->sc_chip.ch_cc_caps_ext = sc->sc_buscore_ops->bc_read(sc,
        core->co_base + BWFM_CHIP_REG_CAPABILITIES_EXT);

    core = bwfm_chip_get_pmu(sc);
    if (sc->sc_chip.ch_cc_caps & BWFM_CHIP_REG_CAPABILITIES_PMU) {
        sc->sc_chip.ch_pmucaps = sc->sc_buscore_ops->bc_read(sc,
            core->co_base + BWFM_CHIP_REG_PMUCAPABILITIES);
        sc->sc_chip.ch_pmurev = sc->sc_chip.ch_pmucaps &
            BWFM_CHIP_REG_PMUCAPABILITIES_REV_MASK;
    }

    if (sc->sc_buscore_ops->bc_setup)
        sc->sc_buscore_ops->bc_setup(sc);

    bwfm_init_board_type(sc);

    return 0;
}

void
bwfm_chip_detach(struct bwfm_softc *sc)
{
    struct bwfm_core *core, *tmp;

    LIST_FOREACH_SAFE(core, &sc->sc_chip.ch_list, co_link, tmp) {
        LIST_REMOVE(core, co_link);
        free(core, M_DEVBUF, sizeof(*core));
    }
}

struct bwfm_core *
bwfm_chip_get_core_idx(struct bwfm_softc *sc, int id, int idx)
{
    struct bwfm_core *core;

    LIST_FOREACH(core, &sc->sc_chip.ch_list, co_link) {
        if (core->co_id == id && idx-- == 0)
            return core;
    }

    return NULL;
}

struct bwfm_core *
bwfm_chip_get_core(struct bwfm_softc *sc, int id)
{
    return bwfm_chip_get_core_idx(sc, id, 0);
}

struct bwfm_core *
bwfm_chip_get_pmu(struct bwfm_softc *sc)
{
    struct bwfm_core *cc, *pmu;

    cc = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_CHIPCOMMON);
    if (cc->co_rev >= 35 && sc->sc_chip.ch_cc_caps_ext &
        BWFM_CHIP_REG_CAPABILITIES_EXT_AOB_PRESENT) {
        pmu = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_PMU);
        if (pmu)
            return pmu;
    }

    return cc;
}

/* Functions for the AI interconnect */
int
bwfm_chip_ai_isup(struct bwfm_softc *sc, struct bwfm_core *core)
{
    uint32_t ioctl, reset;

    ioctl = sc->sc_buscore_ops->bc_read(sc,
        core->co_wrapbase + BWFM_AGENT_IOCTL);
    reset = sc->sc_buscore_ops->bc_read(sc,
        core->co_wrapbase + BWFM_AGENT_RESET_CTL);

    if (((ioctl & (BWFM_AGENT_IOCTL_FGC | BWFM_AGENT_IOCTL_CLK)) ==
        BWFM_AGENT_IOCTL_CLK) &&
        ((reset & BWFM_AGENT_RESET_CTL_RESET) == 0))
        return 1;

    return 0;
}

void
bwfm_chip_ai_disable(struct bwfm_softc *sc, struct bwfm_core *core,
    uint32_t prereset, uint32_t reset)
{
    uint32_t val;
    int i;

    val = sc->sc_buscore_ops->bc_read(sc,
        core->co_wrapbase + BWFM_AGENT_RESET_CTL);
    if ((val & BWFM_AGENT_RESET_CTL_RESET) == 0) {

        sc->sc_buscore_ops->bc_write(sc,
            core->co_wrapbase + BWFM_AGENT_IOCTL,
            prereset | BWFM_AGENT_IOCTL_FGC | BWFM_AGENT_IOCTL_CLK);
        sc->sc_buscore_ops->bc_read(sc,
            core->co_wrapbase + BWFM_AGENT_IOCTL);

        sc->sc_buscore_ops->bc_write(sc,
            core->co_wrapbase + BWFM_AGENT_RESET_CTL,
            BWFM_AGENT_RESET_CTL_RESET);
        delay(20);

        for (i = 300; i > 0; i--) {
            if (sc->sc_buscore_ops->bc_read(sc,
                core->co_wrapbase + BWFM_AGENT_RESET_CTL) ==
                BWFM_AGENT_RESET_CTL_RESET)
                break;
        }
        if (i == 0)
            printf("%s: timeout on core reset\n", DEVNAME(sc));
    }

    sc->sc_buscore_ops->bc_write(sc,
        core->co_wrapbase + BWFM_AGENT_IOCTL,
        reset | BWFM_AGENT_IOCTL_FGC | BWFM_AGENT_IOCTL_CLK);
    sc->sc_buscore_ops->bc_read(sc,
        core->co_wrapbase + BWFM_AGENT_IOCTL);
}

void
bwfm_chip_ai_reset(struct bwfm_softc *sc, struct bwfm_core *core,
    uint32_t prereset, uint32_t reset, uint32_t postreset)
{
    struct bwfm_core *core2 = NULL;
    int i;

    if (core->co_id == BWFM_AGENT_CORE_80211)
        core2 = bwfm_chip_get_core_idx(sc, BWFM_AGENT_CORE_80211, 1);

    bwfm_chip_ai_disable(sc, core, prereset, reset);
    if (core2)
        bwfm_chip_ai_disable(sc, core2, prereset, reset);

    for (i = 50; i > 0; i--) {
        if ((sc->sc_buscore_ops->bc_read(sc,
            core->co_wrapbase + BWFM_AGENT_RESET_CTL) &
            BWFM_AGENT_RESET_CTL_RESET) == 0)
            break;
        sc->sc_buscore_ops->bc_write(sc,
            core->co_wrapbase + BWFM_AGENT_RESET_CTL, 0);
        delay(60);
    }
    if (i == 0)
        printf("%s: timeout on core reset\n", DEVNAME(sc));
    if (core2) {
        for (i = 50; i > 0; i--) {
            if ((sc->sc_buscore_ops->bc_read(sc,
                core2->co_wrapbase + BWFM_AGENT_RESET_CTL) &
                BWFM_AGENT_RESET_CTL_RESET) == 0)
                break;
            sc->sc_buscore_ops->bc_write(sc,
                core2->co_wrapbase + BWFM_AGENT_RESET_CTL, 0);
            delay(60);
        }
        if (i == 0)
            printf("%s: timeout on core reset\n", DEVNAME(sc));
    }

    sc->sc_buscore_ops->bc_write(sc,
        core->co_wrapbase + BWFM_AGENT_IOCTL,
        postreset | BWFM_AGENT_IOCTL_CLK);
    sc->sc_buscore_ops->bc_read(sc,
        core->co_wrapbase + BWFM_AGENT_IOCTL);
    if (core2) {
        sc->sc_buscore_ops->bc_write(sc,
            core2->co_wrapbase + BWFM_AGENT_IOCTL,
            postreset | BWFM_AGENT_IOCTL_CLK);
        sc->sc_buscore_ops->bc_read(sc,
            core2->co_wrapbase + BWFM_AGENT_IOCTL);
    }
}

void
bwfm_chip_dmp_erom_scan(struct bwfm_softc *sc)
{
    uint32_t erom, val, base, wrap;
    uint8_t type = 0;
    uint16_t id;
    uint8_t nmw, nsw, rev;
    struct bwfm_core *core;

    erom = sc->sc_buscore_ops->bc_read(sc,
        BWFM_CHIP_BASE + BWFM_CHIP_REG_EROMPTR);
    while (type != BWFM_DMP_DESC_EOT) {
        val = sc->sc_buscore_ops->bc_read(sc, erom);
        type = val & BWFM_DMP_DESC_MASK;
        erom += 4;

        if (type != BWFM_DMP_DESC_COMPONENT)
            continue;

        id = (val & BWFM_DMP_COMP_PARTNUM)
            >> BWFM_DMP_COMP_PARTNUM_S;

        val = sc->sc_buscore_ops->bc_read(sc, erom);
        type = val & BWFM_DMP_DESC_MASK;
        erom += 4;

        if (type != BWFM_DMP_DESC_COMPONENT) {
            printf("%s: not component descriptor\n", DEVNAME(sc));
            return;
        }

        nmw = (val & BWFM_DMP_COMP_NUM_MWRAP)
            >> BWFM_DMP_COMP_NUM_MWRAP_S;
        nsw = (val & BWFM_DMP_COMP_NUM_SWRAP)
            >> BWFM_DMP_COMP_NUM_SWRAP_S;
        rev = (val & BWFM_DMP_COMP_REVISION)
            >> BWFM_DMP_COMP_REVISION_S;

        if (nmw + nsw == 0 && id != BWFM_AGENT_CORE_PMU &&
            id != BWFM_AGENT_CORE_GCI)
            continue;

        if (bwfm_chip_dmp_get_regaddr(sc, &erom, &base, &wrap))
            continue;

        core = (typeof core)malloc(sizeof(*core), M_DEVBUF, M_WAITOK);
        core->co_id = id;
        core->co_base = base;
        core->co_wrapbase = wrap;
        core->co_rev = rev;
        LIST_INSERT_HEAD(&sc->sc_chip.ch_list, core, co_link);
    }
}

int
bwfm_chip_dmp_get_regaddr(struct bwfm_softc *sc, uint32_t *erom,
    uint32_t *base, uint32_t *wrap)
{
    uint8_t type = 0, mpnum = 0;
    uint8_t stype, sztype, wraptype;
    uint32_t val;

    *base = 0;
    *wrap = 0;

    val = sc->sc_buscore_ops->bc_read(sc, *erom);
    type = val & BWFM_DMP_DESC_MASK;
    if (type == BWFM_DMP_DESC_MASTER_PORT) {
        mpnum = (val & BWFM_DMP_MASTER_PORT_NUM)
            >> BWFM_DMP_MASTER_PORT_NUM_S;
        wraptype = BWFM_DMP_SLAVE_TYPE_MWRAP;
        *erom += 4;
    } else if ((type & ~BWFM_DMP_DESC_ADDRSIZE_GT32) ==
        BWFM_DMP_DESC_ADDRESS)
        wraptype = BWFM_DMP_SLAVE_TYPE_SWRAP;
    else
        return 1;

    do {
        do {
            val = sc->sc_buscore_ops->bc_read(sc, *erom);
            type = val & BWFM_DMP_DESC_MASK;
            if (type == BWFM_DMP_DESC_COMPONENT)
                return 0;
            if (type == BWFM_DMP_DESC_EOT)
                return 1;
            *erom += 4;
        } while ((type & ~BWFM_DMP_DESC_ADDRSIZE_GT32) !=
             BWFM_DMP_DESC_ADDRESS);

        if (type & BWFM_DMP_DESC_ADDRSIZE_GT32)
            *erom += 4;

        sztype = (val & BWFM_DMP_SLAVE_SIZE_TYPE)
            >> BWFM_DMP_SLAVE_SIZE_TYPE_S;
        if (sztype == BWFM_DMP_SLAVE_SIZE_DESC) {
            val = sc->sc_buscore_ops->bc_read(sc, *erom);
            type = val & BWFM_DMP_DESC_MASK;
            if (type & BWFM_DMP_DESC_ADDRSIZE_GT32)
                *erom += 8;
            else
                *erom += 4;
        }
        if (sztype != BWFM_DMP_SLAVE_SIZE_4K &&
            sztype != BWFM_DMP_SLAVE_SIZE_8K)
            continue;

        stype = (val & BWFM_DMP_SLAVE_TYPE) >> BWFM_DMP_SLAVE_TYPE_S;
        if (*base == 0 && stype == BWFM_DMP_SLAVE_TYPE_SLAVE)
            *base = val & BWFM_DMP_SLAVE_ADDR_BASE;
        if (*wrap == 0 && stype == wraptype)
            *wrap = val & BWFM_DMP_SLAVE_ADDR_BASE;
    } while (*base == 0 || *wrap == 0);

    return 0;
}

/* Core configuration */
int
bwfm_chip_set_active(struct bwfm_softc *sc, uint32_t rstvec)
{
    if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4) != NULL)
        return bwfm_chip_cr4_set_active(sc, rstvec);
    if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CA7) != NULL)
        return bwfm_chip_ca7_set_active(sc, rstvec);
    if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CM3) != NULL)
        return bwfm_chip_cm3_set_active(sc);
    return 1;
}

void
bwfm_chip_set_passive(struct bwfm_softc *sc)
{
    if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4) != NULL) {
        bwfm_chip_cr4_set_passive(sc);
        return;
    }
    if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CA7) != NULL) {
        bwfm_chip_ca7_set_passive(sc);
        return;
    }
    if (bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CM3) != NULL) {
        bwfm_chip_cm3_set_passive(sc);
        return;
    }
}

int
bwfm_chip_cr4_set_active(struct bwfm_softc *sc, uint32_t rstvec)
{
    struct bwfm_core *core;

    sc->sc_buscore_ops->bc_activate(sc, rstvec);
    core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4);
    sc->sc_chip.ch_core_reset(sc, core,
        BWFM_AGENT_IOCTL_ARMCR4_CPUHALT, 0, 0);

    return 0;
}

void
bwfm_chip_cr4_set_passive(struct bwfm_softc *sc)
{
    struct bwfm_core *core;
    uint32_t val;
    int i = 0;

    core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CR4);
    val = sc->sc_buscore_ops->bc_read(sc,
        core->co_wrapbase + BWFM_AGENT_IOCTL);
    sc->sc_chip.ch_core_reset(sc, core,
        val & BWFM_AGENT_IOCTL_ARMCR4_CPUHALT,
        BWFM_AGENT_IOCTL_ARMCR4_CPUHALT,
        BWFM_AGENT_IOCTL_ARMCR4_CPUHALT);

    while ((core = bwfm_chip_get_core_idx(sc, BWFM_AGENT_CORE_80211, i++)))
        sc->sc_chip.ch_core_disable(sc, core,
            BWFM_AGENT_D11_IOCTL_PHYRESET |
            BWFM_AGENT_D11_IOCTL_PHYCLOCKEN,
            BWFM_AGENT_D11_IOCTL_PHYCLOCKEN);
}

int
bwfm_chip_ca7_set_active(struct bwfm_softc *sc, uint32_t rstvec)
{
    struct bwfm_core *core;

    sc->sc_buscore_ops->bc_activate(sc, rstvec);
    core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CA7);
    sc->sc_chip.ch_core_reset(sc, core,
        BWFM_AGENT_IOCTL_ARMCR4_CPUHALT, 0, 0);

    return 0;
}

void
bwfm_chip_ca7_set_passive(struct bwfm_softc *sc)
{
    struct bwfm_core *core;
    uint32_t val;

    core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CA7);
    val = sc->sc_buscore_ops->bc_read(sc,
        core->co_wrapbase + BWFM_AGENT_IOCTL);
    sc->sc_chip.ch_core_reset(sc, core,
        val & BWFM_AGENT_IOCTL_ARMCR4_CPUHALT,
        BWFM_AGENT_IOCTL_ARMCR4_CPUHALT,
        BWFM_AGENT_IOCTL_ARMCR4_CPUHALT);

    core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_80211);
    sc->sc_chip.ch_core_reset(sc, core, BWFM_AGENT_D11_IOCTL_PHYRESET |
        BWFM_AGENT_D11_IOCTL_PHYCLOCKEN, BWFM_AGENT_D11_IOCTL_PHYCLOCKEN,
        BWFM_AGENT_D11_IOCTL_PHYCLOCKEN);
}

int
bwfm_chip_cm3_set_active(struct bwfm_softc *sc)
{
    struct bwfm_core *core;

    core = bwfm_chip_get_core(sc, BWFM_AGENT_INTERNAL_MEM);
    if (!sc->sc_chip.ch_core_isup(sc, core))
        return 1;

    sc->sc_buscore_ops->bc_activate(sc, 0);

    core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CM3);
    sc->sc_chip.ch_core_reset(sc, core, 0, 0, 0);

    return 0;
}

void
bwfm_chip_cm3_set_passive(struct bwfm_softc *sc)
{
    struct bwfm_core *core;

    core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_ARM_CM3);
    sc->sc_chip.ch_core_disable(sc, core, 0, 0);
    core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_80211);
    sc->sc_chip.ch_core_reset(sc, core, BWFM_AGENT_D11_IOCTL_PHYRESET |
        BWFM_AGENT_D11_IOCTL_PHYCLOCKEN, BWFM_AGENT_D11_IOCTL_PHYCLOCKEN,
        BWFM_AGENT_D11_IOCTL_PHYCLOCKEN);
    core = bwfm_chip_get_core(sc, BWFM_AGENT_INTERNAL_MEM);
    sc->sc_chip.ch_core_reset(sc, core, 0, 0, 0);

    if (sc->sc_chip.ch_chip == BRCM_CC_43430_CHIP_ID) {
        sc->sc_buscore_ops->bc_write(sc,
            core->co_base + BWFM_SOCRAM_BANKIDX, 3);
        sc->sc_buscore_ops->bc_write(sc,
            core->co_base + BWFM_SOCRAM_BANKPDA, 0);
    }
}

int
bwfm_chip_sr_capable(struct bwfm_softc *sc)
{
    struct bwfm_core *core;
    uint32_t reg;

    if (sc->sc_chip.ch_pmurev < 17)
        return 0;

    switch (sc->sc_chip.ch_chip) {
    case BRCM_CC_4345_CHIP_ID:
    case BRCM_CC_4354_CHIP_ID:
    case BRCM_CC_4356_CHIP_ID:
        core = bwfm_chip_get_pmu(sc);
        sc->sc_buscore_ops->bc_write(sc, core->co_base +
            BWFM_CHIP_REG_CHIPCONTROL_ADDR, 3);
        reg = sc->sc_buscore_ops->bc_read(sc, core->co_base +
            BWFM_CHIP_REG_CHIPCONTROL_DATA);
        return (reg & (1 << 2)) != 0;
    case BRCM_CC_43241_CHIP_ID:
    case BRCM_CC_4335_CHIP_ID:
    case BRCM_CC_4339_CHIP_ID:
        core = bwfm_chip_get_pmu(sc);
        sc->sc_buscore_ops->bc_write(sc, core->co_base +
            BWFM_CHIP_REG_CHIPCONTROL_ADDR, 3);
        reg = sc->sc_buscore_ops->bc_read(sc, core->co_base +
            BWFM_CHIP_REG_CHIPCONTROL_DATA);
        return reg != 0;
    case BRCM_CC_43430_CHIP_ID:
        core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_CHIPCOMMON);
        reg = sc->sc_buscore_ops->bc_read(sc, core->co_base +
            BWFM_CHIP_REG_SR_CONTROL1);
        return reg != 0;
    case CY_CC_4373_CHIP_ID:
        core = bwfm_chip_get_core(sc, BWFM_AGENT_CORE_CHIPCOMMON);
        reg = sc->sc_buscore_ops->bc_read(sc, core->co_base +
            BWFM_CHIP_REG_SR_CONTROL0);
        return (reg & BWFM_CHIP_REG_SR_CONTROL0_ENABLE) != 0;
    case BRCM_CC_4359_CHIP_ID:
    case CY_CC_43752_CHIP_ID:
    case CY_CC_43012_CHIP_ID:
        core = bwfm_chip_get_pmu(sc);
        reg = sc->sc_buscore_ops->bc_read(sc, core->co_base +
            BWFM_CHIP_REG_RETENTION_CTL);
        return (reg & (BWFM_CHIP_REG_RETENTION_CTL_MACPHY_DIS |
                   BWFM_CHIP_REG_RETENTION_CTL_LOGIC_DIS)) == 0;
    case BRCM_CC_4378_CHIP_ID:
        return 0;
    default:
        core = bwfm_chip_get_pmu(sc);
        reg = sc->sc_buscore_ops->bc_read(sc, core->co_base +
            BWFM_CHIP_REG_PMUCAPABILITIES_EXT);
        if ((reg & BWFM_CHIP_REG_PMUCAPABILITIES_SR_SUPP) == 0)
            return 0;

        reg = sc->sc_buscore_ops->bc_read(sc, core->co_base +
            BWFM_CHIP_REG_RETENTION_CTL);
        return (reg & (BWFM_CHIP_REG_RETENTION_CTL_MACPHY_DIS |
                   BWFM_CHIP_REG_RETENTION_CTL_LOGIC_DIS)) == 0;
    }
}

/* RAM size helpers */
void
bwfm_chip_socram_ramsize(struct bwfm_softc *sc, struct bwfm_core *core)
{
    uint32_t coreinfo, nb, lss, banksize, bankinfo;
    uint32_t ramsize = 0, srsize = 0;
    int i;

    if (!sc->sc_chip.ch_core_isup(sc, core))
        sc->sc_chip.ch_core_reset(sc, core, 0, 0, 0);

    coreinfo = sc->sc_buscore_ops->bc_read(sc,
        core->co_base + BWFM_SOCRAM_COREINFO);
    nb = (coreinfo & BWFM_SOCRAM_COREINFO_SRNB_MASK)
        >> BWFM_SOCRAM_COREINFO_SRNB_SHIFT;

    if (core->co_rev <= 7 || core->co_rev == 12) {
        banksize = coreinfo & BWFM_SOCRAM_COREINFO_SRBSZ_MASK;
        lss = (coreinfo & BWFM_SOCRAM_COREINFO_LSS_MASK)
            >> BWFM_SOCRAM_COREINFO_LSS_SHIFT;
        if (lss != 0)
            nb--;
        ramsize = nb * (1 << (banksize + BWFM_SOCRAM_COREINFO_SRBSZ_BASE));
        if (lss != 0)
            ramsize += (1 << ((lss - 1) + BWFM_SOCRAM_COREINFO_SRBSZ_BASE));
    } else {
        for (i = 0; i < nb; i++) {
            sc->sc_buscore_ops->bc_write(sc,
                core->co_base + BWFM_SOCRAM_BANKIDX,
                (BWFM_SOCRAM_BANKIDX_MEMTYPE_RAM <<
                BWFM_SOCRAM_BANKIDX_MEMTYPE_SHIFT) | i);
            bankinfo = sc->sc_buscore_ops->bc_read(sc,
                core->co_base + BWFM_SOCRAM_BANKINFO);
            banksize = ((bankinfo & BWFM_SOCRAM_BANKINFO_SZMASK) + 1)
                * BWFM_SOCRAM_BANKINFO_SZBASE;
            ramsize += banksize;
            if (bankinfo & BWFM_SOCRAM_BANKINFO_RETNTRAM_MASK)
                srsize += banksize;
        }
    }

    switch (sc->sc_chip.ch_chip) {
    case BRCM_CC_4334_CHIP_ID:
        if (sc->sc_chip.ch_chiprev < 2)
            srsize = 32 * 1024;
        break;
    case BRCM_CC_43430_CHIP_ID:
        srsize = 64 * 1024;
        break;
    default:
        break;
    }

    sc->sc_chip.ch_ramsize = ramsize;
    sc->sc_chip.ch_srsize = srsize;
}

void
bwfm_chip_sysmem_ramsize(struct bwfm_softc *sc, struct bwfm_core *core)
{
    uint32_t coreinfo, nb, banksize, bankinfo;
    uint32_t ramsize = 0;
    int i;

    if (!sc->sc_chip.ch_core_isup(sc, core))
        sc->sc_chip.ch_core_reset(sc, core, 0, 0, 0);

    coreinfo = sc->sc_buscore_ops->bc_read(sc,
        core->co_base + BWFM_SOCRAM_COREINFO);
    nb = (coreinfo & BWFM_SOCRAM_COREINFO_SRNB_MASK)
        >> BWFM_SOCRAM_COREINFO_SRNB_SHIFT;

    for (i = 0; i < nb; i++) {
        sc->sc_buscore_ops->bc_write(sc,
            core->co_base + BWFM_SOCRAM_BANKIDX,
            (BWFM_SOCRAM_BANKIDX_MEMTYPE_RAM <<
            BWFM_SOCRAM_BANKIDX_MEMTYPE_SHIFT) | i);
        bankinfo = sc->sc_buscore_ops->bc_read(sc,
            core->co_base + BWFM_SOCRAM_BANKINFO);
        banksize = ((bankinfo & BWFM_SOCRAM_BANKINFO_SZMASK) + 1)
            * BWFM_SOCRAM_BANKINFO_SZBASE;
        ramsize += banksize;
    }

    sc->sc_chip.ch_ramsize = ramsize;
}

void
bwfm_chip_tcm_ramsize(struct bwfm_softc *sc, struct bwfm_core *core)
{
    uint32_t cap, nab, nbb, totb, bxinfo, blksize, ramsize = 0;
    int i;

    cap = sc->sc_buscore_ops->bc_read(sc, core->co_base + BWFM_ARMCR4_CAP);
    nab = (cap & BWFM_ARMCR4_CAP_TCBANB_MASK) >> BWFM_ARMCR4_CAP_TCBANB_SHIFT;
    nbb = (cap & BWFM_ARMCR4_CAP_TCBBNB_MASK) >> BWFM_ARMCR4_CAP_TCBBNB_SHIFT;
    totb = nab + nbb;

    for (i = 0; i < totb; i++) {
        sc->sc_buscore_ops->bc_write(sc,
            core->co_base + BWFM_ARMCR4_BANKIDX, i);
        bxinfo = sc->sc_buscore_ops->bc_read(sc,
            core->co_base + BWFM_ARMCR4_BANKINFO);
        if (bxinfo & BWFM_ARMCR4_BANKINFO_BLK_1K_MASK)
            blksize = 1024;
        else
            blksize = 8192;
        ramsize += ((bxinfo & BWFM_ARMCR4_BANKINFO_BSZ_MASK) + 1) *
            blksize;
    }

    sc->sc_chip.ch_ramsize = ramsize;
}

void
bwfm_chip_tcm_rambase(struct bwfm_softc *sc)
{
    switch (sc->sc_chip.ch_chip) {
    case BRCM_CC_4345_CHIP_ID:
        sc->sc_chip.ch_rambase = 0x198000;
        break;
    case BRCM_CC_4335_CHIP_ID:
    case BRCM_CC_4339_CHIP_ID:
    case BRCM_CC_4350_CHIP_ID:
    case BRCM_CC_4354_CHIP_ID:
    case BRCM_CC_4356_CHIP_ID:
    case BRCM_CC_43567_CHIP_ID:
    case BRCM_CC_43569_CHIP_ID:
    case BRCM_CC_43570_CHIP_ID:
    case BRCM_CC_4358_CHIP_ID:
    case BRCM_CC_43602_CHIP_ID:
    case BRCM_CC_4371_CHIP_ID:
        sc->sc_chip.ch_rambase = 0x180000;
        break;
    case BRCM_CC_43465_CHIP_ID:
    case BRCM_CC_43525_CHIP_ID:
    case BRCM_CC_4365_CHIP_ID:
    case BRCM_CC_4366_CHIP_ID:
    case BRCM_CC_43664_CHIP_ID:
    case BRCM_CC_43666_CHIP_ID:
        sc->sc_chip.ch_rambase = 0x200000;
        break;
    case BRCM_CC_4359_CHIP_ID:
        if (sc->sc_chip.ch_chiprev < 9)
            sc->sc_chip.ch_rambase = 0x180000;
        else
            sc->sc_chip.ch_rambase = 0x160000;
        break;
    case BRCM_CC_4355_CHIP_ID:
    case BRCM_CC_4364_CHIP_ID:
    case CY_CC_4373_CHIP_ID:
        sc->sc_chip.ch_rambase = 0x160000;
        break;
    case BRCM_CC_4377_CHIP_ID:
    case CY_CC_43752_CHIP_ID:
        sc->sc_chip.ch_rambase = 0x170000;
        break;
    case BRCM_CC_4378_CHIP_ID:
        sc->sc_chip.ch_rambase = 0x352000;
        break;
    case BRCM_CC_4387_CHIP_ID:
        sc->sc_chip.ch_rambase = 0x740000;
        break;
    default:
        printf("%s: unknown chip: %d\n", DEVNAME(sc),
            sc->sc_chip.ch_chip);
        break;
    }
}

/* BCDC protocol implementation */
int
bwfm_proto_bcdc_query_dcmd(struct bwfm_softc *sc, int ifidx,
    int cmd, char *buf, size_t *len)
{
    struct bwfm_proto_bcdc_dcmd *dcmd;
    size_t size = sizeof(dcmd->hdr) + *len;
    int ret = 1, reqid;

    reqid = sc->sc_bcdc_reqid++;

    if (*len > sizeof(dcmd->buf))
        return ret;

    dcmd = (typeof dcmd)malloc(size, M_TEMP, M_WAITOK | M_ZERO);
    dcmd->hdr.cmd = htole32(cmd);
    dcmd->hdr.len = htole32(*len);
    dcmd->hdr.flags |= BWFM_BCDC_DCMD_GET;
    dcmd->hdr.flags |= BWFM_BCDC_DCMD_ID_SET(reqid);
    dcmd->hdr.flags |= BWFM_BCDC_DCMD_IF_SET(ifidx);
    dcmd->hdr.flags = htole32(dcmd->hdr.flags);
    memcpy(&dcmd->buf, buf, *len);

    if (bwfm_proto_bcdc_txctl(sc, reqid, (char *)dcmd, &size)) {
        DPRINTF(("%s: tx failed\n", DEVNAME(sc)));
        return ret;
    }

    if (buf) {
        *len = min(*len, size);
        memcpy(buf, dcmd->buf, *len);
    }

    if (dcmd->hdr.flags & BWFM_BCDC_DCMD_ERROR)
        ret = dcmd->hdr.status;
    else
        ret = 0;
    free(dcmd, M_TEMP, size);
    return ret;
}

int
bwfm_proto_bcdc_set_dcmd(struct bwfm_softc *sc, int ifidx,
    int cmd, char *buf, size_t len)
{
    struct bwfm_proto_bcdc_dcmd *dcmd;
    size_t size = sizeof(dcmd->hdr) + len;
    int ret = 1, reqid;

    reqid = sc->sc_bcdc_reqid++;

    if (len > sizeof(dcmd->buf))
        return ret;

    dcmd = (typeof dcmd)malloc(size, M_TEMP, M_WAITOK | M_ZERO);
    dcmd->hdr.cmd = htole32(cmd);
    dcmd->hdr.len = htole32(len);
    dcmd->hdr.flags |= BWFM_BCDC_DCMD_SET;
    dcmd->hdr.flags |= BWFM_BCDC_DCMD_ID_SET(reqid);
    dcmd->hdr.flags |= BWFM_BCDC_DCMD_IF_SET(ifidx);
    dcmd->hdr.flags = htole32(dcmd->hdr.flags);
    memcpy(&dcmd->buf, buf, len);

    if (bwfm_proto_bcdc_txctl(sc, reqid, (char *)dcmd, &size)) {
        DPRINTF(("%s: txctl failed\n", DEVNAME(sc)));
        return ret;
    }

    if (dcmd->hdr.flags & BWFM_BCDC_DCMD_ERROR)
        ret = dcmd->hdr.status;
    else
        ret = 0;
    free(dcmd, M_TEMP, size);
    return ret;
}

int
bwfm_proto_bcdc_txctl(struct bwfm_softc *sc, int reqid, char *buf, size_t *len)
{
    struct bwfm_proto_bcdc_ctl *ctl, *tmp;
    int timeout = 0;

    ctl = (typeof ctl)malloc(sizeof(*ctl), M_TEMP, M_WAITOK|M_ZERO);
    ctl->reqid = reqid;
    ctl->buf = buf;
    ctl->len = *len;

    if (sc->sc_bus_ops->bs_txctl(sc, ctl)) {
        DPRINTF(("%s: tx failed\n", DEVNAME(sc)));
        return 1;
    }

    if (tsleep_nsec(ctl, PWAIT, "bwfm", SEC_TO_NSEC(1)))
        timeout = 1;

    TAILQ_FOREACH_SAFE(ctl, &sc->sc_bcdc_rxctlq, next, tmp) {
        if (ctl->reqid != reqid)
            continue;
        if (ctl->done) {
            TAILQ_REMOVE(&sc->sc_bcdc_rxctlq, ctl, next);
            *len = ctl->len;
            free(ctl, M_TEMP, sizeof(*ctl));
            return 0;
        }
        if (timeout) {
            TAILQ_REMOVE(&sc->sc_bcdc_rxctlq, ctl, next);
            DPRINTF(("%s: timeout waiting for txctl response\n",
                DEVNAME(sc)));
            free(ctl->buf, M_TEMP, ctl->len);
            free(ctl, M_TEMP, sizeof(*ctl));
            return 1;
        }
        break;
    }

    DPRINTF(("%s: did%s find txctl metadata (timeout %d)\n",
        DEVNAME(sc), ctl == NULL ? " not": "", timeout));
    return 1;
}

void
bwfm_proto_bcdc_rxctl(struct bwfm_softc *sc, char *buf, size_t len)
{
    struct bwfm_proto_bcdc_dcmd *dcmd;
    struct bwfm_proto_bcdc_ctl *ctl, *tmp;

    if (len < sizeof(dcmd->hdr))
        return;

    dcmd = (struct bwfm_proto_bcdc_dcmd *)buf;
    dcmd->hdr.cmd = letoh32(dcmd->hdr.cmd);
    dcmd->hdr.len = letoh32(dcmd->hdr.len);
    dcmd->hdr.flags = letoh32(dcmd->hdr.flags);
    dcmd->hdr.status = letoh32(dcmd->hdr.status);

    TAILQ_FOREACH_SAFE(ctl, &sc->sc_bcdc_rxctlq, next, tmp) {
        if (ctl->reqid != BWFM_BCDC_DCMD_ID_GET(dcmd->hdr.flags))
            continue;
        if (ctl->len != len) {
            free(ctl->buf, M_TEMP, ctl->len);
            free(ctl, M_TEMP, sizeof(*ctl));
            return;
        }
        memcpy(ctl->buf, buf, len);
        ctl->done = 1;
        wakeup(ctl);
        return;
    }
}

void
bwfm_proto_bcdc_rx(struct bwfm_softc *sc, mbuf_t m, struct mbuf_list *ml)
{
    struct bwfm_proto_bcdc_hdr *hdr;

    hdr = mtod(m, struct bwfm_proto_bcdc_hdr *);
    if (mbuf_len(m) < sizeof(*hdr)) {
        m_freem(m);
        return;
    }
    if (mbuf_len(m) < sizeof(*hdr) + (hdr->data_offset << 2)) {
        m_freem(m);
        return;
    }
    m_adj(m, sizeof(*hdr) + (hdr->data_offset << 2));

    bwfm_rx(sc, m, ml);
}

/* FW Variable code */
int
bwfm_fwvar_cmd_get_data(struct bwfm_softc *sc, int cmd, void *data, size_t len)
{
    return sc->sc_proto_ops->proto_query_dcmd(sc, 0, cmd, (char *)data, &len);
}

int
bwfm_fwvar_cmd_set_data(struct bwfm_softc *sc, int cmd, void *data, size_t len)
{
    return sc->sc_proto_ops->proto_set_dcmd(sc, 0, cmd, (char *)data, len);
}

int
bwfm_fwvar_cmd_get_int(struct bwfm_softc *sc, int cmd, uint32_t *data)
{
    int ret;
    ret = bwfm_fwvar_cmd_get_data(sc, cmd, data, sizeof(*data));
    *data = letoh32(*data);
    return ret;
}

int
bwfm_fwvar_cmd_set_int(struct bwfm_softc *sc, int cmd, uint32_t data)
{
    data = htole32(data);
    return bwfm_fwvar_cmd_set_data(sc, cmd, &data, sizeof(data));
}

int
bwfm_fwvar_var_get_data(struct bwfm_softc *sc, char *name, void *data, size_t len)
{
    char *buf;
    int ret;

    buf = (typeof buf)malloc(strlen(name) + 1 + len, M_TEMP, M_WAITOK);
    memcpy(buf, name, strlen(name) + 1);
    memcpy(buf + strlen(name) + 1, data, len);
    ret = bwfm_fwvar_cmd_get_data(sc, BWFM_C_GET_VAR,
        buf, strlen(name) + 1 + len);
    memcpy(data, buf, len);
    free(buf, M_TEMP, strlen(name) + 1 + len);
    return ret;
}

int
bwfm_fwvar_var_set_data(struct bwfm_softc *sc, char *name, void *data, size_t len)
{
    char *buf;
    int ret;

    buf = (typeof buf)malloc(strlen(name) + 1 + len, M_TEMP, M_WAITOK);
    memcpy(buf, name, strlen(name) + 1);
    memcpy(buf + strlen(name) + 1, data, len);
    ret = bwfm_fwvar_cmd_set_data(sc, BWFM_C_SET_VAR,
        buf, strlen(name) + 1 + len);
    free(buf, M_TEMP, strlen(name) + 1 + len);
    return ret;
}

int
bwfm_fwvar_var_get_int(struct bwfm_softc *sc, char *name, uint32_t *data)
{
    int ret;
    ret = bwfm_fwvar_var_get_data(sc, name, data, sizeof(*data));
    *data = letoh32(*data);
    return ret;
}

int
bwfm_fwvar_var_set_int(struct bwfm_softc *sc, char *name, uint32_t data)
{
    data = htole32(data);
    return bwfm_fwvar_var_set_data(sc, name, &data, sizeof(data));
}

/* Channel parameters */
uint32_t
bwfm_chan2spec(struct bwfm_softc *sc, struct ieee80211_channel *c)
{
    if (sc->sc_io_type == BWFM_IO_TYPE_D11N)
        return bwfm_chan2spec_d11n(sc, c);
    else
        return bwfm_chan2spec_d11ac(sc, c);
}

uint32_t
bwfm_chan2spec_d11n(struct bwfm_softc *sc, struct ieee80211_channel *c)
{
    uint32_t chanspec;

    chanspec = ieee80211_mhz2ieee(c->ic_freq, 0) & BWFM_CHANSPEC_CHAN_MASK;
    chanspec |= BWFM_CHANSPEC_D11N_SB_N;
    chanspec |= BWFM_CHANSPEC_D11N_BW_20;
    if (IEEE80211_IS_CHAN_2GHZ(c))
        chanspec |= BWFM_CHANSPEC_D11N_BND_2G;
    if (IEEE80211_IS_CHAN_5GHZ(c))
        chanspec |= BWFM_CHANSPEC_D11N_BND_5G;

    return chanspec;
}

uint32_t
bwfm_chan2spec_d11ac(struct bwfm_softc *sc, struct ieee80211_channel *c)
{
    uint32_t chanspec;

    chanspec = ieee80211_mhz2ieee(c->ic_freq, 0) & BWFM_CHANSPEC_CHAN_MASK;
    chanspec |= BWFM_CHANSPEC_D11AC_SB_LLL;
    chanspec |= BWFM_CHANSPEC_D11AC_BW_20;
    if (IEEE80211_IS_CHAN_2GHZ(c))
        chanspec |= BWFM_CHANSPEC_D11AC_BND_2G;
    if (IEEE80211_IS_CHAN_5GHZ(c))
        chanspec |= BWFM_CHANSPEC_D11AC_BND_5G;

    return chanspec;
}

uint32_t
bwfm_spec2chan(struct bwfm_softc *sc, uint32_t chanspec)
{
    if (sc->sc_io_type == BWFM_IO_TYPE_D11N)
        return bwfm_spec2chan_d11n(sc, chanspec);
    else
        return bwfm_spec2chan_d11ac(sc, chanspec);
}

uint32_t
bwfm_spec2chan_d11n(struct bwfm_softc *sc, uint32_t chanspec)
{
    uint32_t chanidx;

    chanidx = chanspec & BWFM_CHANSPEC_CHAN_MASK;

    switch (chanspec & BWFM_CHANSPEC_D11N_BW_MASK) {
    case BWFM_CHANSPEC_D11N_BW_40:
        switch (chanspec & BWFM_CHANSPEC_D11N_SB_MASK) {
        case BWFM_CHANSPEC_D11N_SB_L:
            chanidx -= 2;
            break;
        case BWFM_CHANSPEC_D11N_SB_U:
            chanidx += 2;
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }

    return chanidx;
}

uint32_t
bwfm_spec2chan_d11ac(struct bwfm_softc *sc, uint32_t chanspec)
{
    uint32_t chanidx;

    chanidx = chanspec & BWFM_CHANSPEC_CHAN_MASK;

    switch (chanspec & BWFM_CHANSPEC_D11AC_BW_MASK) {
    case BWFM_CHANSPEC_D11AC_BW_40:
        switch (chanspec & BWFM_CHANSPEC_D11AC_SB_MASK) {
        case BWFM_CHANSPEC_D11AC_SB_LLL:
            chanidx -= 2;
            break;
        case BWFM_CHANSPEC_D11AC_SB_LLU:
            chanidx += 2;
            break;
        default:
            break;
        }
        break;
    case BWFM_CHANSPEC_D11AC_BW_80:
        switch (chanspec & BWFM_CHANSPEC_D11AC_SB_MASK) {
        case BWFM_CHANSPEC_D11AC_SB_LLL:
            chanidx -= 6;
            break;
        case BWFM_CHANSPEC_D11AC_SB_LLU:
            chanidx -= 2;
            break;
        case BWFM_CHANSPEC_D11AC_SB_LUL:
            chanidx += 2;
            break;
        case BWFM_CHANSPEC_D11AC_SB_LUU:
            chanidx += 6;
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }

    return chanidx;
}

/* 802.11 code */
void
bwfm_connect(struct bwfm_softc *sc)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct bwfm_ext_join_params *params;
    uint8_t buf[64];    /* XXX max WPA/RSN/WMM IE length */
    uint8_t *frm;

    /*
     * OPEN: Open or WPA/WPA2 on newer Chips/Firmware.
     * SHARED KEY: WEP.
     * AUTO: Automatic, probably for older Chips/Firmware.
     */
    if (ic->ic_flags & IEEE80211_F_RSNON) {
        uint32_t wsec = 0;
        uint32_t wpa = 0;

        /* tell firmware to add WPA/RSN IE to (re)assoc request */
        if (ic->ic_bss->ni_rsnprotos == IEEE80211_PROTO_RSN)
            frm = ieee80211_add_rsn(buf, ic, ic->ic_bss);
        else
            frm = ieee80211_add_wpa(buf, ic, ic->ic_bss);
        bwfm_fwvar_var_set_data(sc, "wpaie", buf, frm - buf);

        if (ic->ic_rsnprotos & IEEE80211_PROTO_WPA) {
            if (ic->ic_rsnakms & IEEE80211_AKM_PSK)
                wpa |= BWFM_WPA_AUTH_WPA_PSK;
            if (ic->ic_rsnakms & IEEE80211_AKM_8021X)
                wpa |= BWFM_WPA_AUTH_WPA_UNSPECIFIED;
        }
        if (ic->ic_rsnprotos & IEEE80211_PROTO_RSN) {
            if (ic->ic_rsnakms & IEEE80211_AKM_PSK)
                wpa |= BWFM_WPA_AUTH_WPA2_PSK;
            if (ic->ic_rsnakms & IEEE80211_AKM_SHA256_PSK)
                wpa |= BWFM_WPA_AUTH_WPA2_PSK_SHA256;
            if (ic->ic_rsnakms & IEEE80211_AKM_8021X)
                wpa |= BWFM_WPA_AUTH_WPA2_UNSPECIFIED;
            if (ic->ic_rsnakms & IEEE80211_AKM_SHA256_8021X)
                wpa |= BWFM_WPA_AUTH_WPA2_1X_SHA256;
        }
        if (ic->ic_rsnciphers & IEEE80211_WPA_CIPHER_TKIP ||
            ic->ic_rsngroupcipher & IEEE80211_WPA_CIPHER_TKIP)
            wsec |= BWFM_WSEC_TKIP;
        if (ic->ic_rsnciphers & IEEE80211_WPA_CIPHER_CCMP ||
            ic->ic_rsngroupcipher & IEEE80211_WPA_CIPHER_CCMP)
            wsec |= BWFM_WSEC_AES;

        bwfm_fwvar_var_set_int(sc, "wpa_auth", wpa);
        bwfm_fwvar_var_set_int(sc, "wsec", wsec);
    } else {
        bwfm_fwvar_var_set_int(sc, "wpa_auth", BWFM_WPA_AUTH_DISABLED);
        bwfm_fwvar_var_set_int(sc, "wsec", BWFM_WSEC_NONE);
    }
    bwfm_fwvar_var_set_int(sc, "auth", BWFM_AUTH_OPEN);
    bwfm_fwvar_var_set_int(sc, "mfp", BWFM_MFP_NONE);

    if (ic->ic_des_esslen && ic->ic_des_esslen <= BWFM_MAX_SSID_LEN) {
        params = (typeof params)malloc(sizeof(*params), M_TEMP, M_WAITOK | M_ZERO);
        memcpy(params->ssid.ssid, ic->ic_des_essid, ic->ic_des_esslen);
        params->ssid.len = htole32(ic->ic_des_esslen);
        memcpy(params->assoc.bssid, ic->ic_bss->ni_bssid,
            sizeof(params->assoc.bssid));
        params->scan.scan_type = -1;
        params->scan.nprobes = htole32(-1);
        params->scan.active_time = htole32(-1);
        params->scan.passive_time = htole32(-1);
        params->scan.home_time = htole32(-1);
        if (bwfm_fwvar_var_set_data(sc, "join", params, sizeof(*params))) {
            struct bwfm_join_params join;
            memset(&join, 0, sizeof(join));
            memcpy(join.ssid.ssid, ic->ic_des_essid,
                ic->ic_des_esslen);
            join.ssid.len = htole32(ic->ic_des_esslen);
            memcpy(join.assoc.bssid, ic->ic_bss->ni_bssid,
                sizeof(join.assoc.bssid));
            bwfm_fwvar_cmd_set_data(sc, BWFM_C_SET_SSID, &join,
                sizeof(join));
        }
        free(params, M_TEMP, sizeof(*params));
    }
}

#ifndef IEEE80211_STA_ONLY
void
bwfm_hostap(struct bwfm_softc *sc)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct ieee80211_node *ni = ic->ic_bss;
    struct bwfm_join_params join;

    /*
     * OPEN: Open or WPA/WPA2 on newer Chips/Firmware.
     * SHARED KEY: WEP.
     * AUTO: Automatic, probably for older Chips/Firmware.
     */
    if (ic->ic_flags & IEEE80211_F_RSNON) {
        uint32_t wsec = 0;
        uint32_t wpa = 0;

        /* TODO: Turn off if replay counter set */
        if (ni->ni_rsnprotos & IEEE80211_PROTO_RSN)
            bwfm_fwvar_var_set_int(sc, "wme_bss_disable", 1);

        if (ni->ni_rsnprotos & IEEE80211_PROTO_WPA) {
            if (ni->ni_rsnakms & IEEE80211_AKM_PSK)
                wpa |= BWFM_WPA_AUTH_WPA_PSK;
            if (ni->ni_rsnakms & IEEE80211_AKM_8021X)
                wpa |= BWFM_WPA_AUTH_WPA_UNSPECIFIED;
        }
        if (ni->ni_rsnprotos & IEEE80211_PROTO_RSN) {
            if (ni->ni_rsnakms & IEEE80211_AKM_PSK)
                wpa |= BWFM_WPA_AUTH_WPA2_PSK;
            if (ni->ni_rsnakms & IEEE80211_AKM_SHA256_PSK)
                wpa |= BWFM_WPA_AUTH_WPA2_PSK_SHA256;
            if (ni->ni_rsnakms & IEEE80211_AKM_8021X)
                wpa |= BWFM_WPA_AUTH_WPA2_UNSPECIFIED;
            if (ni->ni_rsnakms & IEEE80211_AKM_SHA256_8021X)
                wpa |= BWFM_WPA_AUTH_WPA2_1X_SHA256;
        }
        if (ni->ni_rsnciphers & IEEE80211_WPA_CIPHER_TKIP ||
            ni->ni_rsngroupcipher & IEEE80211_WPA_CIPHER_TKIP)
            wsec |= BWFM_WSEC_TKIP;
        if (ni->ni_rsnciphers & IEEE80211_WPA_CIPHER_CCMP ||
            ni->ni_rsngroupcipher & IEEE80211_WPA_CIPHER_CCMP)
            wsec |= BWFM_WSEC_AES;

        bwfm_fwvar_var_set_int(sc, "wpa_auth", wpa);
        bwfm_fwvar_var_set_int(sc, "wsec", wsec);
    } else {
        bwfm_fwvar_var_set_int(sc, "wpa_auth", BWFM_WPA_AUTH_DISABLED);
        bwfm_fwvar_var_set_int(sc, "wsec", BWFM_WSEC_NONE);
    }
    bwfm_fwvar_var_set_int(sc, "auth", BWFM_AUTH_OPEN);
    bwfm_fwvar_var_set_int(sc, "mfp", BWFM_MFP_NONE);

    bwfm_fwvar_var_set_int(sc, "mpc", 0);
    bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_INFRA, 1);
    bwfm_fwvar_cmd_set_int(sc, BWFM_C_SET_AP, 1);
    bwfm_fwvar_var_set_int(sc, "chanspec",
        bwfm_chan2spec(sc, ic->ic_bss->ni_chan));
    bwfm_fwvar_cmd_set_int(sc, BWFM_C_UP, 1);

    memset(&join, 0, sizeof(join));
    memcpy(join.ssid.ssid, ic->ic_des_essid, ic->ic_des_esslen);
    join.ssid.len = htole32(ic->ic_des_esslen);
    memset(join.assoc.bssid, 0xff, sizeof(join.assoc.bssid));
    bwfm_fwvar_cmd_set_data(sc, BWFM_C_SET_SSID, &join, sizeof(join));
    bwfm_fwvar_var_set_int(sc, "closednet",
        (ic->ic_userflags & IEEE80211_F_HIDENWID) != 0);
}
#endif

void
bwfm_scan_v0(struct bwfm_softc *sc)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct bwfm_escan_params_v0 *params;
    uint32_t nssid = 0, nchan = 0;
    size_t params_size, chan_size, ssid_size;
    struct bwfm_ssid *ssid;

    if (ic->ic_flags & IEEE80211_F_ASCAN &&
        ic->ic_des_esslen && ic->ic_des_esslen <= BWFM_MAX_SSID_LEN)
        nssid = 1;

    chan_size = roundup(nchan * sizeof(uint16_t), sizeof(uint32_t));
    ssid_size = sizeof(struct bwfm_ssid) * nssid;
    params_size = sizeof(*params) + chan_size + ssid_size;

    params = (typeof params)malloc(params_size, M_TEMP, M_WAITOK | M_ZERO);
    ssid = (struct bwfm_ssid *)
        (((uint8_t *)params) + sizeof(*params) + chan_size);

    memset(params->scan_params.bssid, 0xff,
        sizeof(params->scan_params.bssid));
    params->scan_params.bss_type = 2;
    params->scan_params.scan_type = BWFM_SCANTYPE_PASSIVE;
    params->scan_params.nprobes = htole32(-1);
    params->scan_params.active_time = htole32(-1);
    params->scan_params.passive_time = htole32(-1);
    params->scan_params.home_time = htole32(-1);
    params->version = htole32(BWFM_ESCAN_REQ_VERSION);
    params->action = htole16(WL_ESCAN_ACTION_START);
    params->sync_id = htole16(0x1234);

    if (ic->ic_flags & IEEE80211_F_ASCAN &&
        ic->ic_des_esslen && ic->ic_des_esslen <= BWFM_MAX_SSID_LEN) {
        params->scan_params.scan_type = BWFM_SCANTYPE_ACTIVE;
        ssid->len = htole32(ic->ic_des_esslen);
        memcpy(ssid->ssid, ic->ic_des_essid, ic->ic_des_esslen);
    }

    params->scan_params.channel_num = htole32(
        nssid << BWFM_CHANNUM_NSSID_SHIFT |
        nchan << BWFM_CHANNUM_NCHAN_SHIFT);

#if 0
    /* Scan a specific channel */
    params->scan_params.channel_list[0] = htole16(
        (1 & 0xff) << 0 |
        (3 & 0x3) << 8 |
        (2 & 0x3) << 10 |
        (2 & 0x3) << 12
        );
#endif

    bwfm_fwvar_var_set_data(sc, "escan", params, params_size);
    free(params, M_TEMP, params_size);
}

void
bwfm_scan_v2(struct bwfm_softc *sc)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct bwfm_escan_params_v2 *params;
    uint32_t nssid = 0, nchan = 0;
    size_t params_size, chan_size, ssid_size;
    struct bwfm_ssid *ssid;

    if (ic->ic_flags & IEEE80211_F_ASCAN &&
        ic->ic_des_esslen && ic->ic_des_esslen <= BWFM_MAX_SSID_LEN)
        nssid = 1;

    chan_size = roundup(nchan * sizeof(uint16_t), sizeof(uint32_t));
    ssid_size = sizeof(struct bwfm_ssid) * nssid;
    params_size = sizeof(*params) + chan_size + ssid_size;

    params = (typeof params)malloc(params_size, M_TEMP, M_WAITOK | M_ZERO);
    ssid = (struct bwfm_ssid *)
        (((uint8_t *)params) + sizeof(*params) + chan_size);

    params->scan_params.version = 2;
    params->scan_params.length = params_size;
    memset(params->scan_params.bssid, 0xff,
        sizeof(params->scan_params.bssid));
    params->scan_params.bss_type = 2;
    params->scan_params.scan_type = BWFM_SCANTYPE_PASSIVE;
    params->scan_params.nprobes = htole32(-1);
    params->scan_params.active_time = htole32(-1);
    params->scan_params.passive_time = htole32(-1);
    params->scan_params.home_time = htole32(-1);
    params->version = htole32(BWFM_ESCAN_REQ_VERSION_V2);
    params->action = htole16(WL_ESCAN_ACTION_START);
    params->sync_id = htole16(0x1234);

    if (ic->ic_flags & IEEE80211_F_ASCAN &&
        ic->ic_des_esslen && ic->ic_des_esslen <= BWFM_MAX_SSID_LEN) {
        params->scan_params.scan_type = BWFM_SCANTYPE_ACTIVE;
        ssid->len = htole32(ic->ic_des_esslen);
        memcpy(ssid->ssid, ic->ic_des_essid, ic->ic_des_esslen);
    }

    params->scan_params.channel_num = htole32(
        nssid << BWFM_CHANNUM_NSSID_SHIFT |
        nchan << BWFM_CHANNUM_NCHAN_SHIFT);

#if 0
    /* Scan a specific channel */
    params->scan_params.channel_list[0] = htole16(
        (1 & 0xff) << 0 |
        (3 & 0x3) << 8 |
        (2 & 0x3) << 10 |
        (2 & 0x3) << 12
        );
#endif

    bwfm_fwvar_var_set_data(sc, "escan", params, params_size);
    free(params, M_TEMP, params_size);
}

void
bwfm_scan(struct bwfm_softc *sc)
{
    if (sc->sc_scan_ver == 0)
        bwfm_scan_v0(sc);
    else
        bwfm_scan_v2(sc);
}

void
bwfm_scan_abort_v0(struct bwfm_softc *sc)
{
    struct bwfm_escan_params_v0 *params;
    size_t params_size;

    params_size = sizeof(*params) + sizeof(uint16_t);
    params = (typeof params)malloc(params_size, M_TEMP, M_WAITOK | M_ZERO);
    memset(params->scan_params.bssid, 0xff,
        sizeof(params->scan_params.bssid));
    params->scan_params.bss_type = 2;
    params->scan_params.scan_type = BWFM_SCANTYPE_PASSIVE;
    params->scan_params.nprobes = htole32(-1);
    params->scan_params.active_time = htole32(-1);
    params->scan_params.passive_time = htole32(-1);
    params->scan_params.home_time = htole32(-1);
    params->version = htole32(BWFM_ESCAN_REQ_VERSION);
    params->action = htole16(WL_ESCAN_ACTION_START);
    params->sync_id = htole16(0x1234);
    params->scan_params.channel_num = htole32(1);
    params->scan_params.channel_list[0] = htole16(-1);
    bwfm_fwvar_var_set_data(sc, "escan", params, params_size);
    free(params, M_TEMP, params_size);
}

void
bwfm_scan_abort_v2(struct bwfm_softc *sc)
{
    struct bwfm_escan_params_v2 *params;
    size_t params_size;

    params_size = sizeof(*params) + sizeof(uint16_t);
    params = (typeof params)malloc(params_size, M_TEMP, M_WAITOK | M_ZERO);
    params->scan_params.version = 2;
    params->scan_params.length = params_size;
    memset(params->scan_params.bssid, 0xff,
        sizeof(params->scan_params.bssid));
    params->scan_params.bss_type = 2;
    params->scan_params.scan_type = BWFM_SCANTYPE_PASSIVE;
    params->scan_params.nprobes = htole32(-1);
    params->scan_params.active_time = htole32(-1);
    params->scan_params.passive_time = htole32(-1);
    params->scan_params.home_time = htole32(-1);
    params->version = htole32(BWFM_ESCAN_REQ_VERSION_V2);
    params->action = htole16(WL_ESCAN_ACTION_START);
    params->sync_id = htole16(0x1234);
    params->scan_params.channel_num = htole32(1);
    params->scan_params.channel_list[0] = htole16(-1);
    bwfm_fwvar_var_set_data(sc, "escan", params, params_size);
    free(params, M_TEMP, params_size);
}

void
bwfm_scan_abort(struct bwfm_softc *sc)
{
    if (sc->sc_scan_ver == 0)
        bwfm_scan_abort_v0(sc);
    else
        bwfm_scan_abort_v2(sc);
}

mbuf_t
bwfm_newbuf(void)
{
    mbuf_t m;

    MGETHDR(m, M_DONTWAIT, MT_DATA);
    if (m == NULL)
        return (NULL);

    MCLGET(m, M_DONTWAIT);
    if (!(mbuf_flags(m) & M_EXT)) {
        m_freem(m);
        return (NULL);
    }

    mbuf_setlen(m, MCLBYTES);
    mbuf_pkthdr_setlen(m, MCLBYTES);

    return (m);
}

void
bwfm_rx(struct bwfm_softc *sc, mbuf_t m, struct mbuf_list *ml)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct ifnet *ifp = &ic->ic_if;
    struct ieee80211_node *ni;
    struct bwfm_event *e;

#ifdef __STRICT_ALIGNMENT
    /* Remaining data is an ethernet packet, so align. */
    if ((mtod(m, paddr_t) & 0x3) != ETHER_ALIGN) {
        mbuf_t m0;
        m0 = m_dup_pkt(m, ETHER_ALIGN, M_WAITOK);
        m_freem(m);
        if (m0 == NULL) {
            ifp->if_ierrors++;
            return;
        }
        m = m0;
    }
#endif

    e = mtod(m, struct bwfm_event *);
    if (mbuf_len(m) >= sizeof(e->ehdr) &&
        ntohs(e->ehdr.ether_type) == BWFM_ETHERTYPE_LINK_CTL &&
        memcmp(BWFM_BRCM_OUI, e->hdr.oui, sizeof(e->hdr.oui)) == 0 &&
        ntohs(e->hdr.usr_subtype) == BWFM_BRCM_SUBTYPE_EVENT) {
        bwfm_rx_event(sc, m);
        return;
    }

    /* Drop network packets if we are not in RUN state. */
    if (ic->ic_state != IEEE80211_S_RUN) {
        m_freem(m);
        return;
    }

    if ((ic->ic_flags & IEEE80211_F_RSNON) &&
        mbuf_len(m) >= sizeof(e->ehdr) &&
        ntohs(e->ehdr.ether_type) == ETHERTYPE_EAPOL) {
        ifp->if_ipackets++;
#if NBPFILTER > 0
        if (ifp->if_bpf)
            bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_IN);
#endif
#ifndef IEEE80211_STA_ONLY
        if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
            ni = ieee80211_find_node(ic,
                (const u_int8_t *)&e->ehdr.ether_shost);
            if (ni == NULL) {
                m_freem(m);
                return;
            }
        } else
#endif
            ni = ic->ic_bss;
        ieee80211_eapol_key_input(ic, m, ni);
    } else
        ml_enqueue(ml, m);
}

#ifndef IEEE80211_STA_ONLY
void
bwfm_rx_auth_ind(struct bwfm_softc *sc, struct bwfm_event *e, size_t len)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct ifnet *ifp = &ic->ic_if;
    struct ieee80211_rxinfo rxi;
    struct ieee80211_frame *wh;
    mbuf_t m;
    uint32_t pktlen, ieslen;

    /* Build a fake beacon frame to let net80211 do all the parsing. */
    ieslen = betoh32(e->msg.datalen);
    pktlen = sizeof(*wh) + ieslen + 6;
    if (pktlen > MCLBYTES)
        return;
    m = bwfm_newbuf();
    if (m == NULL)
        return;
    wh = mtod(m, struct ieee80211_frame *);
    wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT |
        IEEE80211_FC0_SUBTYPE_AUTH;
    wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
    *(uint16_t *)wh->i_dur = 0;
    IEEE80211_ADDR_COPY(wh->i_addr1, etherbroadcastaddr);
    IEEE80211_ADDR_COPY(wh->i_addr2, &e->msg.addr);
    IEEE80211_ADDR_COPY(wh->i_addr3, ic->ic_bss->ni_bssid);
    *(uint16_t *)wh->i_seq = 0;
    ((uint16_t *)(&wh[1]))[0] = IEEE80211_AUTH_ALG_OPEN;
    ((uint16_t *)(&wh[1]))[1] = IEEE80211_AUTH_OPEN_REQUEST;
    ((uint16_t *)(&wh[1]))[2] = 0;

    /* Finalize mbuf. */
    mbuf_setlen(m, pktlen);
    mbuf_pkthdr_setlen(m, pktlen);
    memset(&rxi, 0, sizeof(rxi));
    ieee80211_input(ifp, m, ic->ic_bss, &rxi);
}

void
bwfm_rx_assoc_ind(struct bwfm_softc *sc, struct bwfm_event *e, size_t len,
    int reassoc)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct ifnet *ifp = &ic->ic_if;
    struct ieee80211_rxinfo rxi;
    struct ieee80211_frame *wh;
    struct ieee80211_node *ni;
    mbuf_t m;
    uint32_t pktlen, ieslen;

    /* Build a fake beacon frame to let net80211 do all the parsing. */
    ieslen = betoh32(e->msg.datalen);
    pktlen = sizeof(*wh) + ieslen + 4;
    if (reassoc)
        pktlen += IEEE80211_ADDR_LEN;
    if (pktlen > MCLBYTES)
        return;
    m = bwfm_newbuf();
    if (m == NULL)
        return;
    wh = mtod(m, struct ieee80211_frame *);
    wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT;
    if (reassoc)
        wh->i_fc[0] |= IEEE80211_FC0_SUBTYPE_REASSOC_REQ;
    else
        wh->i_fc[0] |= IEEE80211_FC0_SUBTYPE_ASSOC_REQ;
    wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
    *(uint16_t *)wh->i_dur = 0;
    IEEE80211_ADDR_COPY(wh->i_addr1, etherbroadcastaddr);
    IEEE80211_ADDR_COPY(wh->i_addr2, &e->msg.addr);
    IEEE80211_ADDR_COPY(wh->i_addr3, ic->ic_bss->ni_bssid);
    *(uint16_t *)wh->i_seq = 0;
    ((uint16_t *)(&wh[1]))[0] = IEEE80211_CAPINFO_ESS; /* XXX */
    ((uint16_t *)(&wh[1]))[1] = 100; /* XXX */
    if (reassoc) {
        memset(((uint8_t *)&wh[1]) + 4, 0, IEEE80211_ADDR_LEN);
        memcpy(((uint8_t *)&wh[1]) + 4 + IEEE80211_ADDR_LEN,
            &e[1], ieslen);
    } else
        memcpy(((uint8_t *)&wh[1]) + 4, &e[1], ieslen);

    /* Finalize mbuf. */
    mbuf_setlen(m, pktlen);
    mbuf_pkthdr_setlen(m, pktlen);
    ni = ieee80211_find_node(ic, wh->i_addr2);
    if (ni == NULL) {
        m_freem(m);
        return;
    }
    memset(&rxi, 0, sizeof(rxi));
    ieee80211_input(ifp, m, ni, &rxi);
}

void
bwfm_rx_deauth_ind(struct bwfm_softc *sc, struct bwfm_event *e, size_t len)
{
    bwfm_rx_leave_ind(sc, e, len, IEEE80211_FC0_SUBTYPE_DEAUTH);
}

void
bwfm_rx_disassoc_ind(struct bwfm_softc *sc, struct bwfm_event *e, size_t len)
{
    bwfm_rx_leave_ind(sc, e, len, IEEE80211_FC0_SUBTYPE_DISASSOC);
}

void
bwfm_rx_leave_ind(struct bwfm_softc *sc, struct bwfm_event *e, size_t len,
    int subtype)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct ifnet *ifp = &ic->ic_if;
    struct ieee80211_rxinfo rxi;
    struct ieee80211_frame *wh;
    struct ieee80211_node *ni;
    mbuf_t m;
    uint32_t pktlen;

    /* Build a fake beacon frame to let net80211 do all the parsing. */
    pktlen = sizeof(*wh) + 2;
    if (pktlen > MCLBYTES)
        return;
    m = bwfm_newbuf();
    if (m == NULL)
        return;
    wh = mtod(m, struct ieee80211_frame *);
    wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT |
        subtype;
    wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
    *(uint16_t *)wh->i_dur = 0;
    IEEE80211_ADDR_COPY(wh->i_addr1, etherbroadcastaddr);
    IEEE80211_ADDR_COPY(wh->i_addr2, &e->msg.addr);
    IEEE80211_ADDR_COPY(wh->i_addr3, ic->ic_bss->ni_bssid);
    *(uint16_t *)wh->i_seq = 0;
    memset((uint8_t *)&wh[1], 0, 2);

    /* Finalize mbuf. */
    mbuf_setlen(m, pktlen);
    mbuf_pkthdr_setlen(m, pktlen);
    ni = ieee80211_find_node(ic, wh->i_addr2);
    if (ni == NULL) {
        m_freem(m);
        return;
    }
    memset(&rxi, 0, sizeof(rxi));
    ieee80211_input(ifp, m, ni, &rxi);
}
#endif

void
bwfm_rx_event(struct bwfm_softc *sc, mbuf_t m)
{
    int s;

    s = splnet();
    ml_enqueue(&sc->sc_evml, m);
    splx(s);

    task_add(sc->sc_taskq, &sc->sc_task);
}

void
bwfm_rx_event_cb(struct bwfm_softc *sc, mbuf_t m)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct ifnet *ifp = &ic->ic_if;
    struct bwfm_event *e = mtod(m, typeof e);
    size_t len = mbuf_len(m);

    if (ntohl(e->msg.event_type) >= BWFM_E_LAST) {
        m_freem(m);
        return;
    }

    switch (ntohl(e->msg.event_type)) {
    case BWFM_E_ESCAN_RESULT: {
        struct bwfm_escan_results *res;
        struct bwfm_bss_info *bss;
        size_t reslen;
        int i;
        /* Abort event triggered by SCAN -> INIT */
        if (ic->ic_state == IEEE80211_S_INIT &&
            ntohl(e->msg.status) == BWFM_E_STATUS_ABORT)
            break;
        if (ic->ic_state != IEEE80211_S_SCAN) {
            DPRINTF(("%s: scan result (%u) while not in SCAN\n",
                DEVNAME(sc), ntohl(e->msg.status)));
            break;
        }
        if (ntohl(e->msg.status) != BWFM_E_STATUS_SUCCESS &&
            ntohl(e->msg.status) != BWFM_E_STATUS_PARTIAL) {
            DPRINTF(("%s: unexpected scan result (%u)\n",
                DEVNAME(sc), ntohl(e->msg.status)));
            break;
        }
        if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS) {
            ieee80211_end_scan(ifp);
            break;
        }
        len -= sizeof(*e);
        if (len < sizeof(*res)) {
            DPRINTF(("%s: results too small\n", DEVNAME(sc)));
            m_freem(m);
            return;
        }
        reslen = len;
        res = (typeof res)malloc(len, M_TEMP, M_WAITOK);
        memcpy(res, (void *)&e[1], len);
        if (len < letoh32(res->buflen)) {
            DPRINTF(("%s: results too small\n", DEVNAME(sc)));
            free(res, M_TEMP, reslen);
            m_freem(m);
            return;
        }
        len -= sizeof(*res);
        if (len < letoh16(res->bss_count) * sizeof(struct bwfm_bss_info)) {
            DPRINTF(("%s: results too small\n", DEVNAME(sc)));
            free(res, M_TEMP, reslen);
            m_freem(m);
            return;
        }
        bss = &res->bss_info[0];
        for (i = 0; i < letoh16(res->bss_count); i++) {
            bwfm_scan_node(sc, &res->bss_info[i], len);
            len -= sizeof(*bss) + letoh32(bss->length);
            bss = (typeof bss)((char *)bss) + letoh32(bss->length);
            if (len <= 0)
                break;
        }
        free(res, M_TEMP, reslen);
        break;
        }
    case BWFM_E_AUTH:
        if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS &&
            ic->ic_state == IEEE80211_S_AUTH)
            ieee80211_new_state(ic, IEEE80211_S_ASSOC, -1);
        else
            ieee80211_begin_scan(ifp);
        break;
    case BWFM_E_ASSOC:
        if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS &&
            ic->ic_state == IEEE80211_S_ASSOC)
            ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
        else if (ntohl(e->msg.status) != BWFM_E_STATUS_UNSOLICITED)
            ieee80211_begin_scan(ifp);
        break;
    case BWFM_E_DEAUTH:
    case BWFM_E_DISASSOC:
        if (ic->ic_state > IEEE80211_S_SCAN)
            ieee80211_begin_scan(ifp);
        break;
    case BWFM_E_LINK:
        if (ntohl(e->msg.status) == BWFM_E_STATUS_SUCCESS &&
            ntohl(e->msg.reason) == 0)
            break;
        /* Link status has changed */
        if (ic->ic_state > IEEE80211_S_SCAN)
            ieee80211_begin_scan(ifp);
        break;
#ifndef IEEE80211_STA_ONLY
    case BWFM_E_AUTH_IND:
        bwfm_rx_auth_ind(sc, e, len);
        break;
    case BWFM_E_ASSOC_IND:
        bwfm_rx_assoc_ind(sc, e, len, 0);
        break;
    case BWFM_E_REASSOC_IND:
        bwfm_rx_assoc_ind(sc, e, len, 1);
        break;
    case BWFM_E_DEAUTH_IND:
        bwfm_rx_deauth_ind(sc, e, len);
        break;
    case BWFM_E_DISASSOC_IND:
        bwfm_rx_disassoc_ind(sc, e, len);
        break;
#endif
    default:
        DPRINTF(("%s: len %lu datalen %u code %u status %u"
            " reason %u\n", __func__, len, ntohl(e->msg.datalen),
            ntohl(e->msg.event_type), ntohl(e->msg.status),
            ntohl(e->msg.reason)));
        break;
    }

    m_freem(m);
}

void
bwfm_scan_node(struct bwfm_softc *sc, struct bwfm_bss_info *bss, size_t len)
{
    struct ieee80211com *ic = &sc->sc_ic;
    struct ifnet *ifp = &ic->ic_if;
    struct ieee80211_frame *wh;
    struct ieee80211_node *ni;
    struct ieee80211_rxinfo rxi;
    mbuf_t m;
    uint32_t pktlen, ieslen;
    uint16_t iesoff;
    int chanidx;

    iesoff = letoh16(bss->ie_offset);
    ieslen = letoh32(bss->ie_length);
    if (ieslen > len - iesoff)
        return;

    /* Build a fake beacon frame to let net80211 do all the parsing. */
    pktlen = sizeof(*wh) + ieslen + 12;
    if (pktlen > MCLBYTES)
        return;
    m = bwfm_newbuf();
    if (m == NULL)
        return;
    wh = mtod(m, struct ieee80211_frame *);
    wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT |
        IEEE80211_FC0_SUBTYPE_BEACON;
    wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
    *(uint16_t *)wh->i_dur = 0;
    IEEE80211_ADDR_COPY(wh->i_addr1, etherbroadcastaddr);
    IEEE80211_ADDR_COPY(wh->i_addr2, bss->bssid);
    IEEE80211_ADDR_COPY(wh->i_addr3, bss->bssid);
    *(uint16_t *)wh->i_seq = 0;
    memset(&wh[1], 0, 12);
    ((uint16_t *)(&wh[1]))[4] = bss->beacon_period;
    ((uint16_t *)(&wh[1]))[5] = bss->capability;
    memcpy(((uint8_t *)&wh[1]) + 12, ((uint8_t *)bss) + iesoff, ieslen);

    /* Finalize mbuf. */
    mbuf_setlen(m, pktlen);
    mbuf_pkthdr_setlen(m, pktlen);
    ni = ieee80211_find_rxnode(ic, wh);
    /* Channel mask equals IEEE80211_CHAN_MAX */
    chanidx = bwfm_spec2chan(sc, letoh32(bss->chanspec));
    /* Supply RSSI */
    memset(&rxi, 0, sizeof(rxi));
    rxi.rxi_rssi = (int16_t)letoh16(bss->rssi);
    rxi.rxi_chan = chanidx;
    ieee80211_input(ifp, m, ni, &rxi);
    /* Node is no longer needed. */
    ieee80211_release_node(ic, ni);
}

void
bwfm_task(void *arg)
{
    struct bwfm_softc *sc = (typeof sc)arg;
    struct bwfm_host_cmd_ring *ring = &sc->sc_cmdq;
    struct bwfm_host_cmd *cmd;
    mbuf_t m;
    int s;

    s = splnet();
    while (ring->next != ring->cur) {
        cmd = &ring->cmd[ring->next];
        splx(s);
        cmd->cb(sc, cmd->data);
        s = splnet();
        ring->queued--;
        ring->next = (ring->next + 1) % BWFM_HOST_CMD_RING_COUNT;
    }
    splx(s);

    s = splnet();
    while ((m = ml_dequeue(&sc->sc_evml)) != NULL) {
        splx(s);
        bwfm_rx_event_cb(sc, m);
        s = splnet();
    }
    splx(s);
}

void
bwfm_do_async(struct bwfm_softc *sc,
    void (*cb)(struct bwfm_softc *, void *), void *arg, int len)
{
    struct bwfm_host_cmd_ring *ring = &sc->sc_cmdq;
    struct bwfm_host_cmd *cmd;
    int s;

    s = splnet();
    KASSERT(ring->queued < BWFM_HOST_CMD_RING_COUNT);
    if (ring->queued >= BWFM_HOST_CMD_RING_COUNT) {
        splx(s);
        return;
    }
    cmd = &ring->cmd[ring->cur];
    cmd->cb = cb;
    KASSERT(len <= sizeof(cmd->data));
    memcpy(cmd->data, arg, len);
    ring->cur = (ring->cur + 1) % BWFM_HOST_CMD_RING_COUNT;
    ring->queued++;
    task_add(sc->sc_taskq, &sc->sc_task);
    splx(s);
}

int
bwfm_send_mgmt(struct ieee80211com *ic, struct ieee80211_node *ni,
    int type, int arg1, int arg2)
{
#ifdef BWFM_DEBUG
    struct bwfm_softc *sc = (typeof sc)ic->ic_softc;
    DPRINTF(("%s: %s\n", DEVNAME(sc), __func__));
#endif
    return 0;
}

int
bwfm_set_key(struct ieee80211com *ic, struct ieee80211_node *ni,
    struct ieee80211_key *k)
{
    struct bwfm_softc *sc = (typeof sc)ic->ic_softc;
    struct bwfm_cmd_key cmd;

    cmd.ni = ni;
    cmd.k = k;
    bwfm_do_async(sc, bwfm_set_key_cb, &cmd, sizeof(cmd));
    sc->sc_key_tasks++;
    return EBUSY;
}

void
bwfm_set_key_cb(struct bwfm_softc *sc, void *arg)
{
    struct bwfm_cmd_key *cmd = (typeof cmd)arg;
    struct ieee80211_key *k = cmd->k;
    struct ieee80211_node *ni = cmd->ni;
    struct ieee80211com *ic = &sc->sc_ic;
    struct bwfm_wsec_key key;
    uint32_t wsec, wsec_enable;
    int ext_key = 0;

    sc->sc_key_tasks--;

    if ((k->k_flags & IEEE80211_KEY_GROUP) == 0 &&
        k->k_cipher != IEEE80211_CIPHER_WEP40 &&
        k->k_cipher != IEEE80211_CIPHER_WEP104)
        ext_key = 1;

    memset(&key, 0, sizeof(key));
    if (ext_key && !IEEE80211_IS_MULTICAST(ni->ni_macaddr))
        memcpy(key.ea, ni->ni_macaddr, sizeof(key.ea));
    key.index = htole32(k->k_id);
    key.len = htole32(k->k_len);
    memcpy(key.data, k->k_key, sizeof(key.data));
    if (!ext_key)
        key.flags = htole32(BWFM_WSEC_PRIMARY_KEY);

    switch (k->k_cipher) {
    case IEEE80211_CIPHER_WEP40:
        key.algo = htole32(BWFM_CRYPTO_ALGO_WEP1);
        wsec_enable = BWFM_WSEC_WEP;
        break;
    case IEEE80211_CIPHER_WEP104:
        key.algo = htole32(BWFM_CRYPTO_ALGO_WEP128);
        wsec_enable = BWFM_WSEC_WEP;
        break;
    case IEEE80211_CIPHER_TKIP:
        key.algo = htole32(BWFM_CRYPTO_ALGO_TKIP);
        wsec_enable = BWFM_WSEC_TKIP;
        break;
    case IEEE80211_CIPHER_CCMP:
        key.algo = htole32(BWFM_CRYPTO_ALGO_AES_CCM);
        wsec_enable = BWFM_WSEC_AES;
        break;
    default:
        printf("%s: cipher %x not supported\n", DEVNAME(sc),
            k->k_cipher);
        ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
        return;
    }

    delay(100);

    bwfm_fwvar_var_set_data(sc, "wsec_key", &key, sizeof(key));
    bwfm_fwvar_var_get_int(sc, "wsec", &wsec);
    wsec |= wsec_enable;
    bwfm_fwvar_var_set_int(sc, "wsec", wsec);

    if (sc->sc_key_tasks == 0) {
        DPRINTF(("%s: marking port %s valid\n", DEVNAME(sc),
            ether_sprintf(cmd->ni->ni_macaddr)));
        cmd->ni->ni_port_valid = 1;
        ieee80211_set_link_state(ic, LINK_STATE_UP);
    }
}

void
bwfm_delete_key(struct ieee80211com *ic, struct ieee80211_node *ni,
    struct ieee80211_key *k)
{
    struct bwfm_softc *sc = (typeof sc)ic->ic_softc;
    struct bwfm_cmd_key cmd;

    cmd.ni = ni;
    cmd.k = k;
    bwfm_do_async(sc, bwfm_delete_key_cb, &cmd, sizeof(cmd));
}

void
bwfm_delete_key_cb(struct bwfm_softc *sc, void *arg)
{
    struct bwfm_cmd_key *cmd = (typeof cmd)arg;
    struct ieee80211_key *k = cmd->k;
    struct bwfm_wsec_key key;

    memset(&key, 0, sizeof(key));
    key.index = htole32(k->k_id);
    key.flags = htole32(BWFM_WSEC_PRIMARY_KEY);
    bwfm_fwvar_var_set_data(sc, "wsec_key", &key, sizeof(key));
}

int
bwfm_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
{
    struct bwfm_softc *sc = (typeof sc)ic->ic_softc;
    struct ifnet *ifp = &ic->ic_if;
    int s;

    s = splnet();

    switch (nstate) {
    case IEEE80211_S_INIT:
        if (ic->ic_state == IEEE80211_S_SCAN)
            bwfm_scan_abort(sc);
        break;
    case IEEE80211_S_SCAN:
#ifndef IEEE80211_STA_ONLY
        /* Don't start a scan if we already have a channel. */
        if (ic->ic_state == IEEE80211_S_INIT &&
            ic->ic_opmode == IEEE80211_M_HOSTAP &&
            ic->ic_des_chan != IEEE80211_CHAN_ANYC) {
            break;
        }
#endif
        /* If we tried to connect, abort. */
        if (ic->ic_state > IEEE80211_S_SCAN)
            bwfm_fwvar_cmd_set_data(sc, BWFM_C_DISASSOC, NULL, 0);
        /* Initiate scan. */
        bwfm_scan(sc);
        if (ifp->if_flags & IFF_DEBUG)
            printf("%s: %s -> %s\n", DEVNAME(sc),
                ieee80211_state_name[ic->ic_state],
                ieee80211_state_name[nstate]);
        /* No need to do this again. */
        if (ic->ic_state == IEEE80211_S_SCAN) {
            splx(s);
            return 0;
        }
        ieee80211_set_link_state(ic, LINK_STATE_DOWN);
        ieee80211_free_allnodes(ic, 1);
        ic->ic_state = nstate;
        splx(s);
        return 0;
    case IEEE80211_S_AUTH:
        ic->ic_bss->ni_rsn_supp_state = RSNA_SUPP_INITIALIZE;
        bwfm_connect(sc);
        if (ifp->if_flags & IFF_DEBUG)
            printf("%s: %s -> %s\n", DEVNAME(sc),
                ieee80211_state_name[ic->ic_state],
                ieee80211_state_name[nstate]);
        ic->ic_state = nstate;
        if (ic->ic_flags & IEEE80211_F_RSNON)
            ic->ic_bss->ni_rsn_supp_state = RSNA_SUPP_PTKSTART;
        splx(s);
        return 0;
#ifndef IEEE80211_STA_ONLY
    case IEEE80211_S_RUN:
        if (ic->ic_opmode == IEEE80211_M_HOSTAP)
            bwfm_hostap(sc);
        break;
#endif
    default:
        break;
    }
    sc->sc_newstate(ic, nstate, arg);
    splx(s);
    return 0;
}

int
bwfm_nvram_convert(int node, u_char **bufp, size_t *sizep, size_t *newlenp)
{
    u_char *src, *dst, *end = *bufp + *sizep, *newbuf;
    size_t count = 0, newsize, pad;
    uint32_t token;
    int skip = 0;

    /*
     * Allocate a new buffer with enough space for the MAC
     * address, padding and final token.
     */
    newsize = *sizep + 64;
    newbuf = (typeof newbuf)malloc(newsize, M_DEVBUF, M_NOWAIT);
    if (newbuf == NULL)
        return 1;

    for (src = *bufp, dst = newbuf; src != end; ++src) {
        if (*src == '\n') {
            if (count > 0)
                *dst++ = '\0';
            count = 0;
            skip = 0;
            continue;
        }
        if (skip)
            continue;
        if (*src == '#' && count == 0) {
            skip = 1;
            continue;
        }
        if (*src == '\r')
            continue;
        *dst++ = *src;
        ++count;
    }

#if defined(__HAVE_FDT)
    /*
     * Append MAC address if one is provided in the device tree.
     * This is needed on Apple Silicon Macs.
     */
    if (node) {
        u_char enaddr[ETHER_ADDR_LEN];
        char macaddr[32];

        if (OF_getprop(node, "local-mac-address",
            enaddr, sizeof(enaddr))) {
            snprintf(macaddr, sizeof(macaddr),
                "macaddr=%02x:%02x:%02x:%02x:%02x:%02x",
                enaddr[0], enaddr[1], enaddr[2], enaddr[3],
                enaddr[4], enaddr[5]);
            if (*dst)
                *dst++ = '\0';
            memcpy(dst, macaddr, strlen(macaddr));
            dst += strlen(macaddr);
        }
    }
#endif

    count = dst - newbuf;
    pad = roundup(count + 1, 4) - count;

    memset(dst, 0, pad);
    count += pad;
    dst += pad;

    token = (count / 4) & 0xffff;
    token |= ~token << 16;
    token = htole32(token);

    memcpy(dst, &token, sizeof(token));
    count += sizeof(token);

    free(*bufp, M_DEVBUF, *sizep);
    *bufp = newbuf;
    *sizep = newsize;
    *newlenp = count;
    return 0;
}

void
bwfm_process_blob(struct bwfm_softc *sc, char *var, u_char **blob,
    size_t *blobsize)
{
    struct bwfm_dload_data *data;
    size_t off, remain, len;

    if (*blob == NULL || *blobsize == 0)
        return;

    off = 0;
    remain = *blobsize;
    data = (typeof data)malloc(sizeof(*data) + BWFM_DLOAD_MAX_LEN, M_TEMP, M_WAITOK);

    while (remain) {
        len = min(remain, BWFM_DLOAD_MAX_LEN);

        data->flag = htole16(BWFM_DLOAD_FLAG_HANDLER_VER_1);
        if (off == 0)
            data->flag |= htole16(BWFM_DLOAD_FLAG_BEGIN);
        if (remain <= BWFM_DLOAD_MAX_LEN)
            data->flag |= htole16(BWFM_DLOAD_FLAG_END);
        data->type = htole16(BWFM_DLOAD_TYPE_CLM);
        data->len = htole32(len);
        data->crc = 0;
        memcpy(data->data, *blob + off, len);

        if (bwfm_fwvar_var_set_data(sc, var, data,
            sizeof(*data) + len)) {
            printf("%s: could not load blob (%s)\n", DEVNAME(sc),
                var);
            goto out;
        }

        off += len;
        remain -= len;
    }

out:
    free(data, M_TEMP, sizeof(*data) + BWFM_DLOAD_MAX_LEN);
    free(*blob, M_DEVBUF, *blobsize);
    *blob = NULL;
    *blobsize = 0;
}

void
bwfm_init_board_type(struct bwfm_softc *sc)
{
#if defined(__HAVE_FDT)
    char compat[128];
    int len;
    char *p;

    len = OF_getprop(OF_peer(0), "compatible", compat, sizeof(compat));
    if (len > 0 && len < sizeof(compat)) {
        compat[len] = '\0';
        if ((p = strchr(compat, '/')) != NULL)
            *p = '\0';
        strlcpy(sc->sc_board_type, compat, sizeof(sc->sc_board_type));
    }
#endif
}

int
bwfm_loadfirmware(struct bwfm_softc *sc, const char *chip, const char *bus,
    u_char **ucode, size_t *size, u_char **nvram, size_t *nvsize, size_t *nvlen)
{
    const char *board_type = NULL;
    char name[128];
    int r;

    *ucode = *nvram = NULL;
    *size = *nvsize = *nvlen = 0;

    if (strlen(sc->sc_board_type) > 0)
        board_type = sc->sc_board_type;

    if (board_type != NULL) {
        r = snprintf(name, sizeof(name), "%sbrcmfmac%s%s.%s.bin",
            sc->sc_fwdir, chip, bus, board_type);
        if ((r > 0 && r < sizeof(name)) &&
            loadfirmware(name, ucode, size) != 0)
            *size = 0;
    }
    if (*size == 0) {
        snprintf(name, sizeof(name), "%sbrcmfmac%s%s.bin",
            sc->sc_fwdir, chip, bus);
        if (loadfirmware(name, ucode, size) != 0) {
            snprintf(name, sizeof(name), "%sbrcmfmac%s%s%s%s.bin",
                sc->sc_fwdir, chip, bus, board_type ? "." : "",
                board_type ? board_type : "");
            printf("%s: failed loadfirmware of file %s\n",
                DEVNAME(sc), name);
            return 1;
        }
    }

    /* .txt needs to be processed first */
    if (strlen(sc->sc_modrev) > 0) {
        r = snprintf(name, sizeof(name),
            "%sbrcmfmac%s%s.%s-%s-%s-%s.txt", sc->sc_fwdir, chip, bus,
            board_type, sc->sc_module, sc->sc_vendor, sc->sc_modrev);
        if (r > 0 && r < sizeof(name))
            loadfirmware(name, nvram, nvsize);
    }
    if (*nvsize == 0 && strlen(sc->sc_vendor) > 0) {
        r = snprintf(name, sizeof(name),
            "%sbrcmfmac%s%s.%s-%s-%s.txt", sc->sc_fwdir, chip, bus,
            board_type, sc->sc_module, sc->sc_vendor);
        if (r > 0 && r < sizeof(name))
            loadfirmware(name, nvram, nvsize);
    }

    if (*nvsize == 0 && board_type != NULL) {
        r = snprintf(name, sizeof(name), "%sbrcmfmac%s%s.%s.txt",
            sc->sc_fwdir, chip, bus, board_type);
        if (r > 0 && r < sizeof(name))
            loadfirmware(name, nvram, nvsize);
    }

    if (*nvsize == 0) {
        snprintf(name, sizeof(name), "%sbrcmfmac%s%s.txt",
            sc->sc_fwdir, chip, bus);
        loadfirmware(name, nvram, nvsize);
    }

    if (*nvsize != 0) {
        if (bwfm_nvram_convert(sc->sc_node, nvram, nvsize, nvlen)) {
            printf("%s: failed to process file %s\n",
                DEVNAME(sc), name);
            free(*ucode, M_DEVBUF, *size);
            free(*nvram, M_DEVBUF, *nvsize);
            return 1;
        }
    }

    /* .nvram is the pre-processed version */
    if (*nvlen == 0) {
        snprintf(name, sizeof(name), "%sbrcmfmac%s%s.nvram",
            sc->sc_fwdir, chip, bus);
        if (loadfirmware(name, nvram, nvsize) == 0)
            *nvlen = *nvsize;
    }

    if (*nvlen == 0 && strcmp(bus, "-sdio") == 0) {
        snprintf(name, sizeof(name), "%sbrcmfmac%s%s%s%s.txt",
            sc->sc_fwdir, chip, bus, board_type ? "." : "",
            board_type ? board_type : "");
        printf("%s: failed loadfirmware of file %s\n",
            DEVNAME(sc), name);
        free(*ucode, M_DEVBUF, *size);
        return 1;
    }

    if (board_type != NULL) {
        r = snprintf(name, sizeof(name), "%sbrcmfmac%s%s.%s.clm_blob",
            sc->sc_fwdir, chip, bus, board_type);
        if (r > 0 && r < sizeof(name))
            loadfirmware(name, &sc->sc_clm, &sc->sc_clmsize);
    }
    if (sc->sc_clmsize == 0) {
        snprintf(name, sizeof(name), "%sbrcmfmac%s%s.clm_blob",
            sc->sc_fwdir, chip, bus);
        loadfirmware(name, &sc->sc_clm, &sc->sc_clmsize);
    }

    if (board_type != NULL) {
        r = snprintf(name, sizeof(name),
            "%sbrcmfmac%s%s.%s.txcap_blob", sc->sc_fwdir,
            chip, bus, board_type);
        if (r > 0 && r < sizeof(name))
            loadfirmware(name, &sc->sc_txcap, &sc->sc_txcapsize);
    }
    if (sc->sc_txcapsize == 0) {
        snprintf(name, sizeof(name), "%sbrcmfmac%s%s.txcap_blob",
            sc->sc_fwdir, chip, bus);
        loadfirmware(name, &sc->sc_txcap, &sc->sc_txcapsize);
    }

    return 0;
}
